В данном разделе рассматриваются некоторые детали реализации, которые зачастую важны для PL/pgSQL необходимо знать пользователям.
SQL-операторы и выражения внутри PL/pgSQL функции могут ссылаться на переменные и параметры этой функции. На внутреннем уровне, PL/pgSQL подставляет параметры запроса вместо таких ссылок. Параметры запроса будут подставляться только в тех местах, где это допустимо синтаксически. В качестве крайнего случая можно рассмотреть следующий пример неудачного стиля программирования:
INSERT INTO foo (foo) VALUES (foo(foo));
Первое вхождение foo синтаксически должно быть именем таблицы, поэтому подстановка не будет выполнена, даже если в функции есть переменная с именем foo. Второе вхождение должно быть именем столбца этой таблицы, поэтому подстановка также не будет произведена. Аналогичным образом, третье вхождение должно быть именем функции, поэтому оно также не будет замещено. Только последнее вхождение может рассматриваться как ссылка на переменную PL/pgSQL
функции.
Иначе говоря, подстановка переменных позволяет только вставлять значения данных в команду SQL; она не может динамически изменять объекты базы данных, на которые ссылается команда. (Для этого необходимо динамически сформировать строку команды, как описано в Раздел 5.6.5.4.)
Поскольку синтаксически имена переменных не отличаются от имен столбцов таблиц, в операторах, где также упоминаются таблицы, может возникнуть неоднозначность: относится ли конкретное имя к столбцу таблицы или к переменной. Рассмотрим изменённый вариант предыдущего примера:
INSERT INTO dest (col) SELECT foo + bar FROM src;
Здесь dest и src должны быть именами таблиц, а
col должен быть столбцом таблицы dest, но foo
и bar могут быть либо переменными функции,
либо столбцами src.
По умолчанию PL/pgSQL будет выдавать ошибку, если имя в SQL-инструкции может относиться как к переменной, так и к столбцу таблицы. Эту проблему можно решить путем переименования переменной или столбца, квалификацией неоднозначной ссылки либо указанием PL/pgSQL какую интерпретацию следует предпочесть.
Самое простое решение — переименовать переменную или столбец.
Общее правило написания кода заключается в использовании
иных соглашений об именовании для PL/pgSQL
переменных, отличных от тех, что используются для имен столбцов. Например,
если единообразно называть переменные функции с префиксом
v_ в то время как ни одно из имен
столбцов не начинается с somethingv_, конфликтов не возникнет.
В качестве альтернативы можно дополнить двусмысленные ссылки квалификаторами, чтобы сделать их однозначными.
В приведенном выше примере src.foo будет однозначной ссылкой на столбец таблицы. Чтобы создать однозначную ссылку на переменную,
следует объявить ее в именованном блоке и использовать метку этого блока
(см. Раздел 5.6.2). Например,
<> DECLARE foo int; BEGIN foo := ...; INSERT INTO dest (col) SELECT block.foo + bar FROM src;
Здесь block.foo означает переменную, даже если существует столбец
foo в src. Параметры функции, а также
специальные переменные, такие как FOUND, могут быть дополнены именем функции, поскольку они неявно объявлены во внешнем блоке, помеченном именем функции.
Иногда исправление всех неоднозначных ссылок в большом объеме PL/pgSQL кода нецелесообразно. В таких случаях можно указать, что PL/pgSQL должен разрешать неоднозначные ссылки в пользу переменной (что соответствует поведению PL/pgSQLповедение до версии Digital Q.DataBase 9.0) или в пользу столбца таблицы (что совместимо с некоторыми другими системами, такими как Oracle).
Чтобы изменить это поведение на уровне всей системы, установите параметр конфигурации plpgsql.variable_conflict в одно из значений:
error, use_variable, или
use_column (где error является заводским значением по умолчанию).
Данный параметр влияет на последующие компиляции
операторов в функциях PL/pgSQL , но не на операторы,
уже скомпилированные в текущем сеансе.
Поскольку изменение этой настройки
может вызвать непредвиденные изменения в поведении функций PL/pgSQL
, оно может быть выполнено только суперпользователем.
Поведение также можно настроить для каждой функции индивидуально, добавив одну из следующих специальных команд в начале текста функции:
#variable_conflict error #variable_conflict use_variable #variable_conflict use_column
Данные команды влияют только на ту функцию, в которой они прописаны, и переопределяют значение параметра plpgsql.variable_conflict. Примером является
CREATE FUNCTION stamp_user(id int, comment text) RETURNS void AS $$
#variable_conflict use_variable
DECLARE
curtime timestamp := now();
BEGIN
UPDATE users SET last_modified = curtime, comment = comment
WHERE users.id = id;
END;
$$ LANGUAGE plpgsql;
В UPDATE команде, curtime, comment,
и id будет ссылаться на переменные и параметры функции независимо от того, users столбцы с такими именами. Обратите внимание, что ссылку на users.id в
WHERE предложение для обеспечения ссылки на столбец таблицы.
При этом ссылку на comment
в качестве целевого элемента в UPDATE списка, так как согласно синтаксису это должен быть столбец таблицы users. Эту же функцию можно было бы написать без использования variable_conflict настройки параметра данным способом:
CREATE FUNCTION stamp_user(id int, comment text) RETURNS void AS $$
<>
DECLARE
curtime timestamp := now();
BEGIN
UPDATE users SET last_modified = fn.curtime, comment = stamp_user.comment
WHERE users.id = stamp_user.id;
END;
$$ LANGUAGE plpgsql;
Подстановка переменных не выполняется в строке команды, передаваемой
в EXECUTE или в один из ее вариантов. При необходимости вставки переменного значения в такую команду это следует делать на этапе формирования строкового значения либо использовать USING, как показано в
Раздел 5.6.5.4.
Подстановка переменных в настоящее время работает только в SELECT,
INSERT, UPDATE,
DELETE, а также в командах, содержащих одну из
них (таких как EXPLAIN и CREATE TABLE
... AS SELECT),
поскольку основной механизм SQL допускает параметры запроса только в этих
командах. Чтобы использовать непостоянное имя или значение в других типах
операторов (называемых вспомогательными), необходимо сформировать
вспомогательный оператор в виде строки и EXECUTE выполнить его.
Синтаксис PL/pgSQL интерпретатор выполняет разбор исходного текста функции и строит внутреннее бинарное дерево инструкций при первом вызове функции (в рамках каждого сеанса). Дерево инструкций полностью преобразует PL/pgSQL структуру операторов, но отдельные SQL выражения и SQL команды, используемые в функции, не преобразуются немедленно.
При первом выполнении каждого выражения и SQL команды в функции PL/pgSQL интерпретатор
выполняет разбор и анализ команды для создания подготовленного оператора,
используя функцию SPI менеджера.
SPI_prepare функции. При последующих обращениях к данному выражению или команде повторно используется подготовленный оператор. Таким образом, функция с редко используемыми условными ветвями кода не повлечет за собой затрат на анализ тех команд, которые не выполняются в рамках текущего сеанса. Недостатком является то, что ошибки в конкретном выражении или команде нельзя обнаружить до тех пор, пока выполнение функции не дойдет до соответствующего фрагмента. (Простые синтаксические ошибки обнаруживаются на этапе первичного разбора, но более серьезные ошибки выявляются только непосредственно при выполнении.)
PL/pgSQL (или, если быть точнее, менеджер SPI) может также попытаться кэшировать план выполнения, связанный с тем или иным конкретным подготовленным оператором. Если кэшированный план не используется, то при каждом обращении к оператору формируется новый план выполнения, и текущие значения параметров (то есть, PL/pgSQL значения переменных) могут быть использованы для оптимизации выбранного плана. Если оператор не имеет параметров или выполняется многократно, менеджер SPI рассмотрит возможность создания общий план, не зависящий от конкретных значений параметров, и его кэширование для повторного использования. Обычно это происходит только в том случае, если план выполнения не очень чувствителен к значениям PL/pgSQL используемых в нем переменных. В противном случае формирование плана при каждом выполнении дает чистый выигрыш. См. PREPARE для получения дополнительной информации о поведении подготовленных операторов.
Поскольку PL/pgSQL сохраняет подготовленные операторы
и в некоторых случаях планы выполнения таким образом,
команды SQL, указанные непосредственно в
PL/pgSQL функции, должны ссылаться на одни и те же таблицы и столбцы при каждом выполнении; то есть нельзя использовать
параметр в качестве имени таблицы или столбца в команде SQL. Чтобы обойти это ограничение, можно формировать динамические команды, используя PL/pgSQL EXECUTE
оператор — ценой проведения повторного синтаксического анализа и построения нового плана выполнения при каждом выполнении.
Изменчивая природа переменных типа record создает еще одну проблему в этой связи. Когда поля переменной типа record используются в выражениях или операторах, типы данных этих полей не должны меняться от одного вызова функции к другому, так как каждое выражение анализируется с использованием типа данных, установленного при первом обращении к этому выражению. EXECUTE можно использовать для решения этой проблемы в случае необходимости.
Если одна и та же функция используется в качестве триггера для нескольких таблиц,
PL/pgSQL подготавливает и кэширует операторы независимо для каждой такой таблицы — то есть кеш создается для каждой комбинации триггерной функции и таблицы, а не просто для каждой функции. Это позволяет частично решить проблемы, связанные с различием типов данных; например, триггерная функция сможет успешно работать со столбцом с именем key даже если она имеет различные типы данных в разных таблицах.
Аналогичным образом для функций с полиморфными типами аргументов создается отдельный кеш операторов для каждой комбинации фактических типов аргументов, с которыми вызывалась функция, что позволяет избежать непредвиденных сбоев из-за различий в типах данных.
Кеширование операторов иногда может приводить к неожиданным результатам при интерпретации значений, чувствительных ко времени. Например, существует разница в поведении следующих двух функций:
CREATE FUNCTION logfunc1(logtxt text) RETURNS void AS $$
BEGIN
INSERT INTO logtable VALUES (logtxt, 'now');
END;
$$ LANGUAGE plpgsql;
и:
CREATE FUNCTION logfunc2(logtxt text) RETURNS void AS $$
DECLARE
curtime timestamp;
BEGIN
curtime := 'now';
INSERT INTO logtable VALUES (logtxt, curtime);
END;
$$ LANGUAGE plpgsql;
В случае функции logfunc1, the
Digital Q.DataBase основному анализатору при разборе известно, INSERT что строка, 'now' должна интерпретироваться как тип данных
timestamp, поскольку целевой столбец таблицы
logtable имеет данный тип. Таким образом,
'now' будет преобразовано в timestamp
константу в момент анализа
INSERT и затем использовано во всех вызовах logfunc1 в течение всего времени
работы сеанса. Разумеется, это не тот результат, который требовался программисту. Целесообразнее использовать now() или
current_timestamp функцию.
В случае функции logfunc2, the
Digital Q.DataBase основной анализатор не знает,
какой тип 'now' должен быть присвоен, и поэтому
он возвращает значение данных типа text содержащее строку
now. В процессе последующего присваивания
локальной переменной curtime, the
PL/pgSQL интерпретатор приводит эту
строку к типу timestamp вызывая для этого
textout и timestamp_in
функции преобразования. Таким образом, вычисляемая отметка времени обновляется при каждом выполнении, как и ожидает разработчик. Хотя такой подход и работает ожидаемым образом, он не отличается высокой эффективностью, поэтому использование now() функции все же будет более правильным решением.