В большом приложении, в котором бизнес-логика базируется на хранимых процедурах, иногда требуется передавать одну и ту информацию между процедурами. И не хочется для этого использовать параметры процедур, так как этой информации может быть много.
Примером подобной информации может быть идентификатор пользователя, от имени которого выполняется хранимая процедура. Так так в трехуровневых приложениях к серверу базы данных подключается не клиентское приложение, а сервер приложений, то переменная SESSION_USER не несет в себе информации об имени пользователе, от имени которого выполняется запрос. В подобной ситуации, если вся бизнес-логика располагается на уровне хранимых процедур, то сервер приложений передает в запросе к базе данных идентификатор пользователя (id). По идентификатору пользователя хранимые процедуры проводят авторизацию пользователя, логируют его действия и делают прочие сервисные операции.
Так как реальная вложенность вызова хранимых процедур может достигать десятков уровней, то каждая хранимая процедура должна иметь возможно передавать идентификатор пользователя любой другой процедуре, которая вызывается у неё внутри. Отсюда следует, что любая хранимая процедура должна иметь возможность принимать идентификатор пользователя как один из её параметров. Иначе в какой-то момент идентификатор пользователя не сможет быть передан какой-то процедуре и придётся переделывать все вызовы изменяемой процедуры, чтобы добавить в неё возможность передачи идентификатора пользователя. Идентификатор пользователя - это один из ярких примеров, лежащих на поверхности, которые могут заставить разработчики начать искать менее трудоемкие возможности сквозной передачи параметров в хранимые процедуры без изменения семантики вызовов этих процедур.
Получается примерно такая картина - из процедуры в процедуры бесконечно передается идентификатор пользователя
В PostgreSQL есть возможность хранить любые значение в пределах одной сессии или транзакции
Примечание
До версии 9.2 см. http://www.postgresql.org/docs/9.1/static/runtime-config-custom.html
Можно создавать свои произвольные настройки, которые подчиняются тем же правилам, что и обычные настройки - установленные значения могут действовать в течение всей сессии или только в рамках транзакции, в которой они были установлены. Эти настройки и будут использоваться как глобальные переменные.
Для того, чтобы создать глобальную переменную необходимо задать её имя с точкой, где до точки будет имя модуля, после точки - имя переменной модуля (см. документацию)
Можно задавать значения по умолчанию по файле postgresql.conf.
Функции для работы с глобальными настройками
current_setting( setting_name ) Возвращает текущее значение настройки/переменной
set_config( setting_name, new_value, is_local ) Устанавливает значение переменной. Если is_local = True, то значение устанавливается только для текущей транзакции, иначе - для всей сессии.
Установка настроек подчиняется правилам транзакций, если транзакция была отменена, то и установленное в этой транзакции значение переменной, не будет сохранено.
В итоге с использованием глобальных переменных картина вызовов изменится следующим образом - исчезнет бесконечно передаваемый из процедуры в процедуру идентификатор пользователя. А за идентификатором пользователя процедуры будут обращаться к глобальной переменной.
Пример простой установки и получения значения переменной для сессии
denis=# SELECT set_config('myapp.var1', '1', False); -- Установка переменной
set_config
------------
1
(1 row)
denis=# SELECT current_setting('myapp.var1'); -- Чтение переменной
current_setting
-----------------
1
(1 row)
denis=# SELECT set_config('myapp.var1', '2', False); -- Установка переменной
set_config
------------
2
(1 row)
denis=# SELECT current_setting('myapp.var1'); -- Чтение нового значение переменной
current_setting
-----------------
2
(1 row)
Пример установки значения переменной для транзакции (начало см. выше)
denis=# SELECT set_config('myapp.var1', '3', True); -- Установка значения переменной для текущей транзакции
set_config
------------
3
(1 row)
denis=# SELECT current_setting('myapp.var1'); -- В переменной хранится старое значение, так как включен autocommit и транзакция действовала в только рамках предыдущего запроса
current_setting
-----------------
2
(1 row)
Что происходит с транзакционной переменной после завершения транзакции
denis=# BEGIN; -- Начало транзакции
BEGIN
denis=# SELECT set_config('myapp.var1', '3', True); -- Установка значения, которое действует только в рамках транзакции
set_config
------------
3
(1 row)
denis=# SELECT current_setting('myapp.var1'); -- Значение сохранилось
current_setting
-----------------
3
(1 row)
denis=# COMMIT; -- Завершаю транзакцию
COMMIT
denis=# SELECT current_setting('myapp.var1'); -- Установленной внутри транзакции значение переменной не сохранилось. Что и ожидалось
current_setting
-----------------
2
(1 row)
Что происходит с сессионной переменной после успешного завершения транзакции
denis=# BEGIN; -- Начало транзакции
BEGIN
denis=# SELECT set_config('myapp.var1', '3', False); -- Установка сессионной переменной
set_config
------------
3
(1 row)
denis=# SELECT set_config('myapp.var1', '4', False); -- Установка другой сессионной переменной
set_config
------------
4
(1 row)
denis=# SELECT current_setting('myapp.var1');
current_setting
-----------------
4
(1 row)
denis=# COMMIT; -- Завершение транзакции
COMMIT
denis=# SELECT current_setting('myapp.var1'); -- Новое значение переменной сохранилось
current_setting
-----------------
4
(1 row)
Что происходит с сессионной переменной после отмены транзакции
denis=# BEGIN; -- Начало транзакции
BEGIN
denis=# SELECT set_config('myapp.var1', '5', False); -- Установка нового значения переменной
set_config
------------
5
(1 row)
denis=# SELECT current_setting('myapp.var1');
current_setting
-----------------
5
(1 row)
denis=# ROLLBACK; -- Откат транзакции
ROLLBACK
denis=# SELECT current_setting('myapp.var1'); -- Новое значение переменной не сохранилось
current_setting
-----------------
4
(1 row)
Преимуществом данного подхода является отсутствие необходимости создавать временные таблицы в базе для хранения значений. Преимущество так же является и с какой-то стороны недостатком - установить значение по умолчанию для переменной можно только в postgresql.conf
В postgresql.conf установлено значение переменной по умолчанию
myapp.debugging = False
Есть функция, которая возвращает значение этой переменной/настройки
CREATE OR REPLACE FUNCTION is_debugging() RETURNS boolean AS
$BODY$
BEGIN
RETURN current_setting('myapp.debugging')::boolean;
END
$BODY$
LANGUAGE plpgsql VOLATILE;
Функция, которая каким-либо способом анализирует значение этой переменной
CREATE OR REPLACE FUNCTION test_debugging() RETURNS void AS
$BODY$
BEGIN
IF is_debugging() THEN
RAISE NOTICE 'is_debugging: True';
ELSE
RAISE NOTICE 'is_debugging: False';
END IF;
-- OR RAISE NOTICE 'is_debugging: %', is_debugging();
END
$BODY$
LANGUAGE plpgsql;
denis=# SELECT is_debugging();
is_debugging
--------------
t
(1 row)
denis=# select test_debugging();
NOTICE: is_debugging: True
test_debugging
----------------
(1 row)
denis=# SELECT set_config('myapp.debugging', 'False', False);
set_config
------------
False
(1 row)
denis=# select test_debugging();
NOTICE: is_debugging: False
test_debugging
----------------
(1 row)
Если хочется оптимизировать вызов is_debugging, надо сменить тип функции с VOLATILE на IMMUTABLE. В этом случае результат первого выполнения функции кешируется и при последующих вызовах результат выполнения будет взят из кеша, даже если значение переменной изменится в рамках текущей сессии. Подробнее об оптимизации читайте статью "Константы в приложении".