Comment arrêter conditionnellement un script psql (basé sur une valeur variable)?

10

Prenons l'exemple suivant (depuis le début d'un script psql):

\c :db_to_run_on

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

Maintenant, s'il est exécuté par la commande

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

alors il s'exécute et l'utilisateur est content. Mais que faire s'il décide de préciser -v db_to_run_on=production_database? (Supposons que cela puisse arriver, tout comme les gens courent rm -rf / # don't try this at home!!!occasionnellement.) J'espère qu'il y a une nouvelle sauvegarde de cette table ...

La question se pose donc: comment vérifier les variables transmises à un script et arrêter le traitement en fonction de leur valeur?

dezso
la source

Réponses:

13

Il y a une option psqlqui arrête l'exécution des commandes en cas d'erreur, c'est ON_ERROR_STOP. Si nous pouvions soulever une erreur, cela ferait ce que nous voulons.

Le problème est que nous devons tester la variable et produire une erreur d'une manière ou d'une autre. Puisqu'on ne peut pas utiliser les structures de contrôle dans psql(car il n'y en a pas) *, ma seule idée était d'utiliser SQL pour les tests. Eh bien, produire une erreur de manière conditionnelle est quelque chose de pl/pgsqltrès bon, alors j'ai écrit une fonction qui générerait une erreur. Je peux maintenant appeler cette fonction à partir d'une CASEstructure simple . Un exemple simple:

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

*: Vous pouvez utiliser toutes les commandes de shell après \!et les conditions du shell, mais depuis l' \!ouverture d'un nouveau shell, exécuter quoi que ce soit là n'a aucun effet pour le script psql actuel.

dezso
la source
\set ON_ERROR_STOP on- agréable!
msciwoj
5

PostgreSQL 10

PostgreSQL 10 apporte des conditions à psql. Ce n'est plus un problème.

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

Je suppose que vous pourriez également utiliser DO..

\if :db_to_run_on != 'dev_database'
do $$
  BEGIN
    RAISE 'Meaningful error message here';
  END;
$$ LANGUAGE plpgsql;
\endif
Evan Carroll
la source
... n'est plus un problème si vous utilisez PostgreSQL 10.
Steve Bennett
1
@SteveBennett est assez clair à ce sujet. Mais je pense que ce n'est pas tout à fait vrai. Vous n'avez besoin que du psql sur la version 10, pas du serveur principal.
Evan Carroll
Oh c'est intéressant. Mais oui, les anciennes versions peuvent rester longtemps.
Steve Bennett
Vous pouvez également \set ON_ERROR_STOP 1et ensuite \if yes \endifexiger la version 10 ou supérieure de psql. :) (Les versions antérieures se plaindront d' \ifêtre invalides, puis quitteront.)
Wildcard
1

Ce que j'ai trouvé fonctionne très bien pour moi, c'est d'utiliser un langage de script pour générer un fichier SQL que je dirige ensuite dans psql, quelque chose comme ceci:

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

Ensuite, j'appelle cela à partir d'un script de pilote:

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

Mon script de pilote est généralement un fichier Rake, mais vous avez l'idée.

François Beausoleil
la source
2
Hé bien oui. Je l'ai eu :) Bien que j'apprécie votre contribution, c'est exactement ce que je veux éviter - en utilisant une couche supplémentaire.
dezso
1

Une version plus concise de la réponse de dezso:

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

Vous pouvez alors appeler cela comme:

\set ON_ERROR_STOP on

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