Wie stoppe ich ein psql-Skript bedingt (basierend auf einem variablen Wert)?

10

Betrachten wir das folgende Beispiel (vom Beginn eines psql-Skripts an):

\c :db_to_run_on

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

Nun, wenn es vom Befehl ausgeführt wird

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

dann läuft es einfach und der Benutzer ist glücklich. Aber was ist, wenn er sich entscheidet zu spezifizieren -v db_to_run_on=production_database? (Nehmen wir an, dass dies passieren kann, genau wie die Leute rm -rf / # don't try this at home!!!gelegentlich laufen .) Hoffentlich gibt es eine neue Sicherung dieser Tabelle ...

Es stellt sich also die Frage: Wie können die an ein Skript übergebenen Variablen überprüft und die weitere Verarbeitung basierend auf ihrem Wert gestoppt werden?

dezso
quelle

Antworten:

13

Es gibt eine Option, bei der psqldie Ausführung von Befehlen bei einem Fehler beendet wird ON_ERROR_STOP. Wenn wir irgendwie einen Fehler auslösen könnten, würde dies tun, was wir wollen.

Das Problem ist, dass wir die Variable testen und irgendwie einen Fehler erzeugen müssen. Da man in psql(weil es keine gibt) * keine Kontrollstrukturen verwenden kann , war meine einzige Idee, SQL zum Testen zu verwenden. Nun, einen Fehler bedingt zu erzeugen, ist etwas, das pl/pgsqlziemlich gut ist, also habe ich eine Funktion geschrieben, die einen Fehler erzeugen würde. Ich kann diese Funktion jetzt von einer einfachen CASEStruktur aus aufrufen . Ein einfaches Beispiel:

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

*: Sie können beliebige Shell-Befehle nach \!und Bedingungen der Shell verwenden. Da jedoch \!eine neue Shell geöffnet wird, hat die Ausführung von Elementen dort keine Auswirkungen auf das aktuelle psql-Skript.

dezso
quelle
\set ON_ERROR_STOP on- nett!
msciwoj
5

PostgreSQL 10

PostgreSQL 10 bringt Bedingungen zu psql. Dies ist kein Problem mehr.

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

Ich denke, Sie könnten auch verwenden DO..

\if :db_to_run_on != 'dev_database'
do $$
  BEGIN
    RAISE 'Meaningful error message here';
  END;
$$ LANGUAGE plpgsql;
\endif
Evan Carroll
quelle
... kein Problem mehr, wenn Sie zufällig PostgreSQL 10 ausführen.
Steve Bennett
1
@SteveBennett ziemlich klar darüber. Aber ich denke es ist nicht ganz wahr. Sie benötigen nur die psql in Version 10, nicht das Server-Backend.
Evan Carroll
Oh, das ist interessant. Aber ja, alte Versionen können eine lange Zeit bleiben.
Steve Bennett
Sie können auch \set ON_ERROR_STOP 1und dann \if yes \endifpsql Version 10 oder höher benötigen. :) (Frühere Versionen werden sich über \ifdie Ungültigkeit beschweren und dann beenden.)
Wildcard
1

Was für mich sehr gut funktioniert, ist die Verwendung einer Skriptsprache, um eine SQL-Datei zu generieren, die ich dann in psql weitergebe.

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

Dann rufe ich dies aus einem Treiberskript auf:

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

Mein Treiberskript ist normalerweise eine Rake-Datei, aber Sie haben die Idee.

François Beausoleil
quelle
2
Nun ja. Ich habe es verstanden :) Obwohl ich Ihre Eingabe sehr schätze, ist dies genau das, was ich vermeiden möchte - mit einer zusätzlichen Ebene.
Dekso
1

Eine präzisere Version von dezsos Antwort:

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

Sie können dies dann wie folgt nennen:

\set ON_ERROR_STOP on

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