¿Cómo detener condicionalmente un script psql (basado en un valor variable)?

10

Consideremos el siguiente ejemplo (desde el comienzo de un script psql):

\c :db_to_run_on

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

Ahora si es ejecutado esto por el comando

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

entonces simplemente se ejecuta y el usuario está contento. Pero, ¿qué pasa si decide especificar -v db_to_run_on=production_database? (Supongamos que esto puede suceder, al igual que las personas corren rm -rf / # don't try this at home!!!ocasionalmente.) Esperemos que haya una copia de seguridad nueva de esa tabla ...

Entonces surge la pregunta: ¿cómo verificar las variables pasadas a un script y detener el procesamiento posterior en función de su valor?

dezso
fuente

Respuestas:

13

Hay una opción en la psqlque deja de ejecutar comandos por error, esto es ON_ERROR_STOP. Si pudiéramos generar un error de alguna manera, esto haría lo que queramos.

El problema es que tenemos que probar la variable y producir un error de alguna manera. Como no se pueden usar estructuras de control en psql(porque no hay ninguna) *, mi única idea era usar SQL para las pruebas. Bueno, producir un error condicionalmente es algo pl/pgsqlbastante bueno, así que escribí una función que generaría un error. Ahora puedo llamar a esta función desde una CASEestructura simple . Un simple ejemplo:

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

*: Puede usar cualquier comando de shell después \!y condicionales del shell, pero desde que \!abre un nuevo shell, ejecutar cualquier cosa allí no tiene ningún efecto para el script psql actual.

dezso
fuente
\set ON_ERROR_STOP on- ¡bonito!
msciwoj
5

PostgreSQL 10

PostgreSQL 10 trae condicionales a psql. Esto ya no es un problema.

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

Supongo que también podrías usar DO...

\if :db_to_run_on != 'dev_database'
do $$
  BEGIN
    RAISE 'Meaningful error message here';
  END;
$$ LANGUAGE plpgsql;
\endif
Evan Carroll
fuente
... ya no es un problema si estás ejecutando PostgreSQL 10.
Steve Bennett
1
@SteveBennett es bastante claro al respecto. Pero creo que no es del todo cierto. Solo necesita el psql en la versión 10, no el servidor del servidor.
Evan Carroll
Oh eso es interesante. Pero sí, las versiones antiguas pueden durar mucho tiempo.
Steve Bennett
También puede \set ON_ERROR_STOP 1y luego \if yes \endifrequerir psql versión 10 o superior. :) (Las versiones anteriores se quejarán por \ifno ser válidas y luego se cerrarán.)
Comodín el
1

Lo que encontré funciona muy bien para mí es usar un lenguaje de script para generar un archivo SQL que luego canalizo en psql, algo como esto:

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

Entonces, llamo a esto desde un script de controlador:

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

Mi script de controlador suele ser un archivo Rake, pero ya se entiende la idea.

François Beausoleil
fuente
2
Bueno, sí. Lo tengo :) Aunque agradezco su aporte, esto es exactamente lo que quiero evitar: usar una capa adicional.
dezso
1

Una versión más concisa de la respuesta de dezso:

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

Luego puede llamar a esto como:

\set ON_ERROR_STOP on

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