Jak warunkowo zatrzymać skrypt psql (oparty na wartości zmiennej)?

10

Rozważmy następujący przykład (od początku skryptu psql):

\c :db_to_run_on

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

Teraz, jeśli jest to uruchomione przez polecenie

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

to po prostu działa i użytkownik jest zadowolony. Ale co jeśli zdecyduje się określić -v db_to_run_on=production_database? (Załóżmy, że może się to zdarzyć, tak jak ludzie biegają rm -rf / # don't try this at home!!!okazjonalnie.) Mam nadzieję, że istnieje nowa kopia zapasowa tego stołu ...

Powstaje więc pytanie: jak sprawdzić zmienne przekazane do skryptu i zatrzymać dalsze przetwarzanie na podstawie ich wartości?

dezso
źródło

Odpowiedzi:

13

Istnieje opcja, w psqlktórej przestaje wykonywać polecenia po błędzie, to jest ON_ERROR_STOP. Gdybyśmy mogli jakoś zgłosić błąd, zrobiłby to, co chcemy.

Problem polega na tym, że musimy przetestować zmienną i jakoś wygenerować błąd. Ponieważ nie można używać struktur kontrolnych w psql(ponieważ nie ma żadnych) *, moim jedynym pomysłem było użycie SQL do testowania. Warunkowo generowanie błędu jest czymś pl/pgsqlcałkiem dobrym, więc napisałem funkcję, która wygeneruje błąd. Mogę teraz wywołać tę funkcję z prostej CASEstruktury. Prosty przykład:

-- 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;

*: Możesz użyć dowolnych poleceń powłoki \!i warunków powłoki, ale ponieważ \!otwiera ona nową powłokę, wykonywanie czegokolwiek nie ma żadnego wpływu na bieżący skrypt psql.

dezso
źródło
\set ON_ERROR_STOP on- miły!
msciwoj,
5

PostgreSQL 10

PostgreSQL 10 dostarcza warunkowe do psql. To już nie jest problem.

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

Myślę, że możesz też użyć DO...

\if :db_to_run_on != 'dev_database'
do $$
  BEGIN
    RAISE 'Meaningful error message here';
  END;
$$ LANGUAGE plpgsql;
\endif
Evan Carroll
źródło
... nie stanowi już problemu, jeśli używasz PostgreSQL 10.
Steve Bennett,
1
@ SteveBennett dość jasno o tym. Ale myślę, że to nie do końca prawda. Potrzebujesz tylko psql w wersji 10, a nie backend serwera.
Evan Carroll,
Och, to interesujące. Ale tak, stare wersje mogą pozostać przez dłuższy czas.
Steve Bennett,
Możesz także, \set ON_ERROR_STOP 1a następnie \if yes \endifwymagać psql w wersji 10 lub wyższej. :) (Wcześniejsze wersje będą narzekać na to, że \ifsą nieprawidłowe, a następnie rezygnują).
Wildcard
1

To, co znalazłem, działa dla mnie bardzo dobrze, to użyć języka skryptowego do wygenerowania pliku SQL, który następnie przesyłam do psql, mniej więcej tak:

#!/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"

Następnie wywołuję to ze skryptu sterownika:

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

Mój skrypt sterownika jest zwykle plikiem Rake, ale masz pomysł.

François Beausoleil
źródło
2
No tak. Rozumiem :) Chociaż doceniam twój wkład, tego właśnie chcę uniknąć - używając dodatkowej warstwy.
dezso,
1

Bardziej zwięzła wersja odpowiedzi dezso:

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

Następnie możesz nazwać to tak:

\set ON_ERROR_STOP on

SELECT CASE WHEN (
  SELECT COUNT(*) FROM mytable
) > 0 THEN pg_temp.err('Already loaded') END;
Steve Bennett
źródło