Разрешение имен функций PL / pgSQL с вложенными функциями

У меня есть две похожие схемы в одной базе данных с одинаковыми именами функций. Каждая схема принадлежит роли, которая соответствует имени схемы.

У меня есть проблемы с разрешением имени функции с вложенными функциями. Я ожидал, что внешняя функция вызовет внутренние функции в той же схеме, но это не так! Имя разрешается динамически на основе search_path во время выполнения, что имеет некоторый смысл, но не так, как я.

Вот тестовый пример. Скажем, например, что схемы и роли называются test и prod, как показано ниже.

Test schema:
CREATE ROLE test NOLOGIN;
CREATE SCHEMA test AUTHORIZATION test;

CREATE OR REPLACE FUNCTION test.inner_func() RETURNS TEXT
AS $BODY$
BEGIN
   RETURN 'test function';
END
$BODY$ LANGUAGE 'plpgsql';
ALTER FUNCTION test.inner_func() OWNER TO test;

CREATE OR REPLACE FUNCTION test.outer_func() RETURNS SETOF TEXT
AS $BODY$
BEGIN
   RETURN QUERY SELECT inner_func();
END
$BODY$ LANGUAGE 'plpgsql';
ALTER FUNCTION test.outer_func() OWNER TO test;
Prod schema:
CREATE ROLE prod NOLOGIN;
CREATE SCHEMA prod AUTHORIZATION prod;

CREATE OR REPLACE FUNCTION prod.inner_func() RETURNS TEXT
AS $BODY$
BEGIN
   RETURN 'prod function';
END
$BODY$ LANGUAGE 'plpgsql';
ALTER FUNCTION prod.inner_func() OWNER TO prod;

CREATE OR REPLACE FUNCTION prod.outer_func() RETURNS SETOF TEXT
AS $BODY$
BEGIN
   RETURN QUERY SELECT inner_func();
END
$BODY$ LANGUAGE 'plpgsql';
ALTER FUNCTION prod.outer_func() OWNER TO prod;
Test cases:
SET search_path=test,public;    
SELECT outer_func();
> test function

SELECT prod.outer_func();
> test function <<<---- was expecting prod function 

SET search_path=prod,public;
SELECT prod.outer_func();
> prod function

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

Я могу добиться такого поведения, используя SECURITY DEFINER функции с динамическим SQL и CURRENT_USER, но я ищу что-то более простое.


person lessj    schedule 01.05.2015    source источник
comment
Вы можете установить требуемый путь поиска как атрибут функции (используя SET в определении функции). Кстати: название языка является идентификатором, не заключайте его в одинарные кавычки. Это устаревший синтаксис, который будет удален в будущем.   -  person a_horse_with_no_name    schedule 01.05.2015
comment
@ a_horse_with_no_name Отлично! работать так хорошо. Пожалуйста, используйте кнопку ответа вместо комментария, чтобы я мог выставить баллы и установить ответ на вопрос.   -  person lessj    schedule 01.05.2015
comment
@a_horse_with_no_name правильное. Но я не уверен, почему вы просто не устанавливаете search_path после подключения. То, что вы здесь делаете, немного похоже на довольно распространенный мультитенантный шаблон, в котором вы создаете серию идентичных схем на основе имени клиента, а затем, когда вы подключаетесь, вы устанавливаете путь поиска для текущего клиента. Запуск системы в качестве теста устанавливает search_path после подключения и т. Д.   -  person David S    schedule 01.05.2015
comment
@Davis S. Допустим, у вас есть команда из 7-8 пользователей tier1 и tier3, у которых есть собственная учетная запись для управления несколькими схемами клиентов. У администраторов баз данных уровня 3 не будет проблем с установкой пути поиска, но они могут легко забыть и напортачить. Однако вы, конечно, можете представить себе ущерб, который может нанести поддержка Tier1, которая используется для работы с Oracle.   -  person lessj    schedule 02.05.2015


Ответы (1)


Чистое решение - либо квалифицировать функцию по схеме:

CREATE OR REPLACE FUNCTION test.outer_func()
  RETURNS SETOF text AS
$func$
BEGIN
   RETURN QUERY SELECT test.inner_func();
END
$func$  LANGUAGE plpgsql;  -- no quotes!

Или вы явно устанавливаете search_path для каждой функции. Вы можете установить параметры конфигурации следующим образом:

CREATE OR REPLACE FUNCTION test.outer_func()
  RETURNS SETOF text AS
$func$
BEGIN
   RETURN QUERY SELECT inner_func();
END
$func$  LANGUAGE plpgsql SET search_path = test, pg_temp;

Настройте search_path в соответствии с вашими потребностями, возможно, добавьте public в список. Я помещаю pg_temp в конец, чтобы объекты во временной схеме не могли скрыть сохраненные объекты. (Но это не применимо к функциям.) Подобно тому, что объясняется в руководство по SECURITY DEFINER функциям.

Я бы не советовал полагаться на то, что пользователь устанавливает правильный search_path. Это имело бы смысл только для «общедоступных» функций, это не соответствовало бы вашему дизайну. Зачем создавать отдельные функции и все же полагаться на пользовательские настройки? Для начала у вас может быть одна функция в общедоступной схеме, но я бы в любом случае не получил этого маршрута. Очень запутанный и подверженный ошибкам.

Кроме того, PL / pgSQL выполняет внутри себя такие операторы, как подготовленные операторы. каждый раз, когда вы меняете search_path, все "подготовленные" операторы из функций plpgsql должны быть освобождены, что не помогает оптимизировать производительность.

Фактически, ваш тестовый пример в вопросе работает, только если вы сначала установите search_path:

SET search_path=test,public;

Иначе вы получите сообщение об ошибке при попытке создать

CREATE OR REPLACE FUNCTION test.outer_func() RETURNS SETOF TEXT
AS $BODY$
BEGIN
   RETURN QUERY SELECT inner_func();
...
ERROR: function inner_func() does not exist

Проверки синтаксиса выполняются для текущего search_path во время создания - если вы не предоставите search_path, как предлагается. Это было исправлено в 2010 году после того, как я сообщил об ошибке.

Подробности для search_path:

И не цитируйте название языка. Это идентификатор.

person Erwin Brandstetter    schedule 02.05.2015