10.3. Функции#

10.3. Функции

10.3. Функции #

Определение конкретной функции, на которую ссылается вызов функции, происходит с использованием следующей процедуры.

Определение типа функции

  1. Выберите функции, которые будут рассмотрены из системного каталога pg_proc. Если использовано имя функции без указания схемы, рассматриваются функции с соответствующим именем и количеством аргументов, которые видны в текущем пути поиска (см. Раздел 5.9.3). Если указано полное имя функции, рассматриваются только функции в указанной схеме.

    1. Если путь поиска находит несколько функций с одинаковыми типами аргументов, рассматривается только та, которая появляется раньше в пути. Функции с разными типами аргументов рассматриваются на равных независимо от позиции в пути поиска.

    2. Если функция объявлена с параметром массива VARIADIC, и вызов не использует ключевое слово VARIADIC, то функция рассматривается так, как если бы параметр массива был заменен одним или несколькими вхождениями его элементного типа, по мере необходимости для соответствия вызову. После такого расширения функция может иметь эффективные типы аргументов, идентичные некоторой не-вариативной функции. В этом случае используется функция, появляющаяся раньше в пути поиска, или, если две функции находятся в одной схеме, предпочтение отдается не-вариативной функции.

      Это создает угрозу безопасности при вызове через полное имя [10] , функция с переменным числом аргументов, найденная в схеме, которая позволяет ненадежным пользователям создавать объекты. Злоумышленник может получить контроль и выполнять произвольные SQL-функции, как если бы вы их выполнили. Замените вызов, содержащий ключевое слово VARIADIC, которое обходит эту опасность. Вызовы, заполняющие параметры VARIADIC "any", часто не имеют эквивалентной формулировки, содержащей ключевое слово VARIADIC. Чтобы выполнять эти вызовы безопасно, схема функции должна разрешать создание объектов только доверенным пользователям.

    3. Функции, которые имеют значения по умолчанию для параметров, считаются соответствующими любому вызову, который опускает одну или несколько позиций параметров по умолчанию. Если более одной такой функции соответствует вызову, используется та, которая появляется раньше в пути поиска. Если в одной схеме есть две или более таких функции с идентичными типами параметров в позициях без значений по умолчанию (что возможно, если у них разные наборы параметров по умолчанию), система не сможет определить, какую предпочесть, и поэтому возникнет ошибка "неоднозначного вызова функции", если не будет найдено лучшее соответствие вызову.

      Это создает нежелательную вероятность недоступности при вызове через полное имя [10] , любая функция, найденная в схеме, которая позволяет ненадежным пользователям создавать объекты. Злоумышленник может создать функцию с именем существующей функции, повторяя параметры этой функции и добавляя новые параметры со значениями по умолчанию. Это исключает возможность вызова оригинальной функции. Чтобы предотвратить эту угрозу, помещайте функции в схемы, в которых только доверенные пользователи могут создавать объекты.

  2. Проверьте наличие функции, принимающей точно такие же типы аргументов. Если такая функция существует (в наборе рассматриваемых функций может быть только одно точное совпадение), используйте ее. Отсутствие точного совпадения создает угрозу безопасности при вызове через полное имя [10] , функция, найденная в схеме, которая позволяет ненадежным пользователям создавать объекты. В таких ситуациях приводите аргументы к точному соответствию. (Случаи, связанные с unknown, никогда не найдут совпадение на этом этапе).

  3. Если точное совпадение не найдено, проверьте, является ли вызов функции запросом на особый тип преобразования. Это происходит, если вызов функции имеет только один аргумент и имя функции совпадает с (внутренним) именем некоторого типа данных. Кроме того, аргумент функции должен быть либо литералом неизвестного типа, либо типом, который может быть преобразован в указанный тип данных путем применения функций ввода-вывода этого типа (то есть преобразование либо к одному из стандартных строковых типов, либо от них). Когда выполняются эти условия, вызов функции рассматривается как форма спецификации CAST. [11]

  4. Ищите наилучшее совпадение.

    1. Отбросьте кандидатские функции, для которых типы входных данных не совпадают и не могут быть преобразованы (с использованием неявного преобразования) для совпадения. Литералы типа unknown считаются преобразуемыми в любой тип для этой цели. Если остается только один кандидат, используйте его; в противном случае перейдите к следующему шагу.

    2. Если любой входной аргумент является типом домена, считайте его типом базового типа домена для всех последующих шагов. Это гарантирует, что домены действуют как их базовые типы для целей разрешения неоднозначных функций.

    3. Пройдите через всех кандидатов и оставьте только тех, у которых есть наиболее точные совпадения с типами ввода. Оставьте всех кандидатов, если нет точных совпадений. Если остается только один кандидат, используйте его; в противном случае перейдите к следующему шагу.

    4. Пройдите через всех кандидатов и оставьте только тех, которые принимают предпочтительные типы (категории типов входных данных) на наибольшем количестве позиций, где потребуется преобразование типов. Оставьте всех кандидатов, если ни один из них не принимает предпочтительные типы. Если остается только один кандидат, используйте его; в противном случае перейдите к следующему шагу.

    5. Если какие-либо входные аргументы имеют тип unknown, проверьте категории типов, принимаемые на этих позициях аргументов оставшимися кандидатами. На каждой позиции выберите категорию string, если какой-либо кандидат принимает эту категорию. (Это предпочтение в пользу строки является уместным, поскольку литерал неизвестного типа выглядит как строка.) В противном случае, если все оставшиеся кандидаты принимают одну и ту же категорию типов, выберите эту категорию; в противном случае не удалось определить правильный выбор без дополнительных подсказок. Теперь отбросьте кандидаты, которые не принимают выбранную категорию типов. Кроме того, если какой-либо кандидат принимает предпочтительный тип в этой категории, отбросьте кандидаты, которые принимают непредпочтительные типы для этого аргумента. Сохраните все кандидаты, если ни один из них не проходит эти тесты. Если остается только один кандидат, используйте его; в противном случае перейдите к следующему шагу.

    6. Если есть и аргументы с известным типом unknown, и все аргументы с известным типом имеют одинаковый тип, предположим, что аргументы unknown также имеют этот тип и проверим, какие кандидаты могут принять этот тип на позициях аргументов unknown. Если этот тест проходит только для одного кандидата, используйте его. В противном случае, завершите выполнение.

Обратите внимание, что правила "наилучшего соответствия" идентичны для определения типов операторов и функций. Приведены некоторые примеры.

Пример 10.6. Определение типа аргумента функции округления

Есть только одна функция round, которая принимает два аргумента; первый аргумент имеет тип numeric, а второй аргумент имеет тип integer. Таким образом, следующий запрос автоматически преобразует первый аргумент типа integer в тип numeric:

SELECT round(4, 4);

 round
--------
 4.0000
(1 row)

Этот запрос фактически преобразуется парсером в:

SELECT round(CAST (4 AS numeric), 4);

Поскольку числовые константы с десятичными точками изначально присваиваются типу numeric, следующий запрос не потребует преобразования типов и, следовательно, может быть немного более эффективным:

SELECT round(4.0, 4);


Пример 10.7. Разрешение вариативной функции

CREATE FUNCTION public.variadic_example(VARIADIC numeric[]) RETURNS int
  LANGUAGE sql AS 'SELECT 1';
CREATE FUNCTION

Эта функция принимает, но не требует ключевого слова VARIADIC. Она допускает как целочисленные, так и числовые аргументы:

SELECT public.variadic_example(0),
       public.variadic_example(0.0),
       public.variadic_example(VARIADIC array[0.0]);
 variadic_example | variadic_example | variadic_example
------------------+------------------+------------------
                1 |                1 |                1
(1 row)

Однако, первый и второй вызовы будут предпочитать более конкретные функции, если они доступны:

CREATE FUNCTION public.variadic_example(numeric) RETURNS int
  LANGUAGE sql AS 'SELECT 2';
CREATE FUNCTION

CREATE FUNCTION public.variadic_example(int) RETURNS int
  LANGUAGE sql AS 'SELECT 3';
CREATE FUNCTION

SELECT public.variadic_example(0),
       public.variadic_example(0.0),
       public.variadic_example(VARIADIC array[0.0]);
 variadic_example | variadic_example | variadic_example
------------------+------------------+------------------
                3 |                2 |                1
(1 row)

Учитывая конфигурацию по умолчанию и только первую существующую функцию, первый и второй вызовы являются небезопасными. Любой пользователь может перехватить их, создав вторую или третью функцию. Путем точного сопоставления типа аргумента и использования ключевого слова VARIADIC третий вызов является безопасным.


Пример 10.8. Тип определения функции подстроки

Существует несколько функций substr, одна из которых принимает аргументы типов text и integer. Если вызвать эту функцию с константой строкового типа неопределенного типа, система выберет функцию-кандидата, которая принимает аргумент предпочтительной категории string (то есть типа text).

SELECT substr('1234', 3);

 substr
--------
     34
(1 row)

Если строка объявлена как тип varchar, что может быть случаем, если она получена из таблицы, то парсер попытается преобразовать ее в тип text:

SELECT substr(varchar '1234', 3);

 substr
--------
     34
(1 row)

Это преобразуется парсером и фактически становится:

SELECT substr(CAST (varchar '1234' AS text), 3);

Примечание

Синтаксический анализатор изучает каталог pg_cast и узнает, что типы text и varchar совместимы в двоичном формате, что означает, что один тип может быть передан функции, принимающей другой тип, без физического преобразования. Поэтому в этом случае фактически не вставляется вызов преобразования типов.

И если функция вызывается с аргументом типа integer, парсер попытается преобразовать его в text:

SELECT substr(1234, 3);
ERROR:  function substr(integer, integer) does not exist
HINT:  No function matches the given name and argument types. You might need
to add explicit type casts.

Это не работает, потому что integer не имеет неявного преобразования в text. Однако, явное преобразование будет работать:

SELECT substr(CAST (1234 AS text), 3);

 substr
--------
     34
(1 row)




[10] Возможность возникновения опасности не возникает при использовании имени без указания схемы, поскольку поиск по пути, содержащему схемы, позволяющие ненадежным пользователям создавать объекты, не является безопасным шаблоном использования схемы.

[11] Причина этого шага заключается в поддержке спецификаций приведения типов в стиле функций в случаях, когда фактической функции приведения нет. Если есть функция приведения, она обычно называется по имени своего выходного типа, поэтому нет необходимости в специальном случае. См. CREATE CAST для дополнительных комментариев.