Как условно остановить сценарий psql (на основе значения переменной)?

10

Давайте рассмотрим следующий пример (с самого начала сценария psql):

\c :db_to_run_on

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

Теперь, если это запустится командой

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

тогда он просто запускается, и пользователь счастлив. Но что если он (и) решит уточнить -v db_to_run_on=production_database? (Предположим, что это может произойти, точно так же, как люди бегут rm -rf / # don't try this at home!!!случайно). Надеюсь, есть свежая резервная копия этой таблицы ...

Таким образом, возникает вопрос: как проверить переменные, переданные в сценарий, и остановить дальнейшую обработку на основе их значения?

Dezso
источник

Ответы:

13

Есть опция, при psqlкоторой выполнение команд прекращается при ошибке, это ON_ERROR_STOP. Если бы мы могли как-то вызвать ошибку, это бы делало то, что мы хотим.

Проблема в том, что мы должны протестировать переменную и как-то выдать ошибку. Поскольку нельзя использовать управляющие структуры в psql(потому что их нет) *, моя единственная идея состояла в том, чтобы использовать SQL для тестирования. Что ж, условно выдавать ошибку - это то, что pl/pgsqlнеплохо, поэтому я написал функцию, которая выдаст ошибку. Теперь я могу вызвать эту функцию из простой CASEструктуры. Простой пример:

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

*: Вы можете использовать любые команды оболочки после \!и условия оболочки, но поскольку \!открывается новая оболочка, выполнение чего-либо там не имеет никакого эффекта для текущего сценария psql.

Dezso
источник
\set ON_ERROR_STOP on- отлично!
msciwoj
5

PostgreSQL 10

PostgreSQL 10 приносит условные выражения в psql. Это больше не проблема.

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

Я думаю, вы могли бы также использовать DO..

\if :db_to_run_on != 'dev_database'
do $$
  BEGIN
    RAISE 'Meaningful error message here';
  END;
$$ LANGUAGE plpgsql;
\endif
Эван Кэрролл
источник
... больше не проблема, если вы работаете с PostgreSQL 10.
Стив Беннетт
1
@ SteveBennett довольно ясно об этом. Но я думаю, что это не совсем так. Вам нужен только psql в версии 10, а не серверная часть.
Эван Кэрролл,
О, это интересно. Но да, старые версии могут остаться на долгое время.
Стив Беннетт
Вы также можете \set ON_ERROR_STOP 1и затем \if yes \endifтребовать PSQL версии 10 или выше. :) (Ранние версии будут жаловаться \ifна недействительность, а затем выход.)
Wildcard
1

То, что я нашел, работает очень хорошо для меня, это использовать язык сценариев для генерации файла SQL, который я затем передаю в psql, что-то вроде этого:

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

Затем я вызываю это из скрипта драйвера:

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

Мой скрипт драйвера обычно является файлом Rake, но вы поняли идею.

Франсуа Босолей
источник
2
Ну да. Я понял :) Хотя я ценю ваш вклад, это именно то, чего я хочу избежать - используя дополнительный слой.
Дезсо
1

Более краткая версия ответа Дезсо:

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

Вы можете тогда назвать это как:

\set ON_ERROR_STOP on

SELECT CASE WHEN (
  SELECT COUNT(*) FROM mytable
) > 0 THEN pg_temp.err('Already loaded') END;
Стив Беннетт
источник