Como parar condicionalmente um script psql (com base em um valor variável)?

10

Vamos considerar o seguinte exemplo (desde o início de um script psql):

\c :db_to_run_on

TRUNCATE the_most_important_table;
-- tried to avoid similarities to anything that exists out there

Agora, se for executado isso pelo comando

psql [connection details] -v db_to_run_on=\'dev_database\'

então ele é executado e o usuário fica feliz. Mas e se ele decidir especificar -v db_to_run_on=production_database? (Vamos supor que isso possa acontecer, assim como as pessoas correm rm -rf / # don't try this at home!!!ocasionalmente.) Espero que haja um novo backup dessa tabela ...

Portanto, surge a pergunta: como verificar as variáveis ​​passadas para um script e interromper o processamento com base em seu valor?

dezso
fonte

Respostas:

13

Existe uma opção em psqlque para de executar comandos por erro, é isso ON_ERROR_STOP. Se pudéssemos gerar um erro de alguma forma, isso faria o que queremos.

O problema é que temos que testar a variável e produzir um erro de alguma forma. Como não se pode usar estruturas de controle em psql(porque não há nenhuma) *, minha única ideia era usar SQL para teste. Bem, produzir um erro condicionalmente é algo pl/pgsqlbom, então escrevi uma função que geraria um erro. Agora posso chamar essa função a partir de uma CASEestrutura simples . Um exemplo simples:

-- let's assume for clarity that there is no function with this name in the database
CREATE OR REPLACE FUNCTION error_generator()
RETURNS boolean AS
$body$
BEGIN
    RAISE 'Meaningful error message here';
    RETURN FALSE; -- just for aesthetical purposes
END;
$body$
LANGUAGE plpgsql;

\set ON_ERROR_STOP on

BEGIN;

-- test for the variable value
-- notice that if :var is not set, it fails as well (with a syntax error)
SELECT CASE WHEN 1 = :var THEN error_generator() ELSE TRUE END;

INSERT INTO test_table (integer_value, text_value)
VALUES (:var, 'something');

COMMIT;

*: Você pode usar qualquer comando do shell após \!e condicionais do shell, mas como \!abre um novo shell, a execução de qualquer coisa existente não tem efeito no script psql atual.

dezso
fonte
\set ON_ERROR_STOP on- agradável!
msciwoj
5

PostgreSQL 10

O PostgreSQL 10 traz condicionais para o psql. Isso não é mais um problema.

\if :db_to_run_on = 'dev_database'
  TRUNCATE the_most_important_table;
\endif

Eu acho que você também pode usar DO..

\if :db_to_run_on != 'dev_database'
do $$
  BEGIN
    RAISE 'Meaningful error message here';
  END;
$$ LANGUAGE plpgsql;
\endif
Evan Carroll
fonte
... não é mais um problema se você estiver executando o PostgreSQL 10. #
Steve Bennett
11
@SteveBennett bem claro sobre isso. Mas acho que não é totalmente verdade. Você só precisa do psql na versão 10, não do servidor back-end.
Evan Carroll
Oh isso é interessante. Mas sim, as versões antigas podem durar muito tempo.
Steve Bennett
Você pode também \set ON_ERROR_STOP 1e, em seguida, \if yes \endifpara exigir versão psql 10 ou superior. :) (As versões anteriores reclamam por \ifserem inválidas e, em seguida, são encerradas.) #
Wildcard
1

O que eu descobri que funciona muito bem para mim é usar uma linguagem de script para gerar um arquivo SQL que, em seguida, canalizo para o psql, algo como isto:

#!/usr/bin/env ruby

raise "Not a good database name: #{ARGV.first.inspect}" unless ARGV.first =~ /^(dev|test)/

puts "\\timing off"
puts "set client_min_messages='warning';"
puts
puts "TRUNCATE the_most_important_table;"
puts "-- more commands"

Então, eu chamo isso de um script de driver:

#!/bin/bash
/usr/bin/ruby generator ${1} | /usr/bin/psql --dbname=${1} --file=- --single-transaction

Meu script de driver geralmente é um arquivo Rake, mas você entendeu.

François Beausoleil
fonte
2
Bem, sim. Entendi :) Embora aprecie sua opinião, é exatamente isso que quero evitar - usando uma camada adicional.
dezso 25/09/12
1

Uma versão mais concisa da resposta de dezso:

CREATE OR REPLACE FUNCTION pg_temp.err(msg varchar) RETURNS boolean     
AS $$ BEGIN RAISE '%',msg; END; $$ LANGUAGE plpgsql;

Você pode chamar assim:

\set ON_ERROR_STOP on

SELECT CASE WHEN (
  SELECT COUNT(*) FROM mytable
) > 0 THEN pg_temp.err('Already loaded') END;
Steve Bennett
fonte