10.2. Операторы#

10.2. Операторы

10.2. Операторы #

Определение конкретного оператора, на который ссылается операторное выражение, выполняется с использованием следующей процедуры. Обратите внимание, что эта процедура косвенно зависит от приоритета задействованных операторов, так как это определит, какие подвыражения будут приняты в качестве входных данных для каких операторов. Дополнительную информацию см. в разделе Раздел 4.1.6.

Определение типа оператора

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

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

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

    1. Если один из аргументов вызова бинарного оператора имеет тип unknown, то предполагается, что он имеет тот же тип, что и другой аргумент для этой проверки. Вызовы, включающие два аргумента типа unknown или префиксный оператор с аргументом типа unknown, никогда не найдут совпадение на этом этапе.

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

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

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

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

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

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

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

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

Следуют некоторые примеры.

Пример 10.1. Тип определения оператора квадратного корня

В стандартном каталоге определен только один оператор квадратного корня (префиксный |/), который принимает аргумент типа double precision. Сканер назначает начальный тип integer аргументу в данном выражении запроса:

SELECT |/ 40 AS "square root of 40";
 square root of 40
-------------------
 6.324555320336759
(1 row)

Таким образом, парсер выполняет преобразование типов операнда, и запрос эквивалентен:

SELECT |/ CAST(40 AS double precision) AS "square root of 40";


Пример 10.2. Определение типа оператора конкатенации строк

Для работы со строковыми типами и сложными типами расширений используется синтаксис, похожий на строку. Строки с неуказанным типом сопоставляются с возможными кандидатами на операторы.

Пример с одним неопределенным аргументом:

SELECT text 'abc' || 'def' AS "text and unknown";

 text and unknown
------------------
 abcdef
(1 row)

В этом случае парсер проверяет, есть ли оператор, принимающий text в качестве обоих аргументов. Поскольку такой оператор существует, он предполагает, что второй аргумент должен быть интерпретирован как тип text.

Вот объединение двух значений неопределенных типов:

SELECT 'abc' || 'def' AS "unspecified";

 unspecified
-------------
 abcdef
(1 row)

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


Пример 10.3. Абсолютное значение и определение типа оператора отрицания

Каталог операторов Tantor BE содержит несколько записей для префиксного оператора @, все из которых реализуют операции нахождения абсолютного значения для различных числовых типов данных. Одна из этих записей относится к типу float8, который является предпочтительным типом в категории числовых данных. Поэтому Tantor BE будет использовать эту запись при обработке входных данных типа unknown.

SELECT @ '-4.5' AS "abs";
 abs
-----
 4.5
(1 row)

Здесь система неявно преобразовала неизвестный литерал типа в тип float8 перед применением выбранного оператора. Можно проверить, что использовался именно тип float8, а не какой-то другой:

SELECT @ '-4.5e500' AS "abs";

ERROR:  "-4.5e500" is out of range for type double precision

С другой стороны, префиксный оператор ~ (побитовая инверсия) определен только для целочисленных типов данных, а не для float8. Так что, если мы попробуем аналогичный случай с ~, мы получим:

SELECT ~ '20' AS "negation";

ERROR:  operator is not unique: ~ "unknown"
HINT:  Could not choose a best candidate operator. You might need to add
explicit type casts.

Это происходит потому, что система не может определить, какой из нескольких возможных операторов ~ следует предпочесть. Можно помочь ей с явным приведением типа:

SELECT ~ CAST('20' AS int8) AS "negation";

 negation
----------
      -21
(1 row)


Пример 10.4. Оператор включения массива: определение типа

Вот еще один пример разрешения оператора с одним известным и одним неизвестным входом:

SELECT array[1,2] <@ '{1,2,3}' as "is subset";

 is subset
-----------
 t
(1 row)

Каталог операторов Tantor BE содержит несколько записей для инфиксного оператора <@, но только две из них могут принимать целочисленный массив в качестве левого операнда: включение массива (anyarray <@ anyarray) и включение диапазона (anyelement <@ anyrange). Поскольку ни один из этих псевдотипов полиморфных типов (см. Раздел 8.21) не считается предпочтительным, парсер не может разрешить неоднозначность на этой основе. Однако Step 3.f указывает парсеру предположить, что литерал неизвестного типа имеет тот же тип, что и другой операнд, то есть целочисленный массив. Теперь только один из двух операторов может соответствовать, поэтому выбрано включение массива. (Если бы было выбрано включение диапазона, мы получили бы ошибку, потому что строка не имеет правильного формата для представления диапазонного литерала).


Пример 10.5. Пользовательский оператор на типе домена

Пользователи иногда пытаются объявить операторы, применяемые только к типу домена. Это возможно, но далеко не так полезно, как может показаться, потому что правила разрешения операторов разработаны для выбора операторов, применяемых к базовому типу домена. В качестве примера рассмотрим

CREATE DOMAIN mytext AS text CHECK(...);
CREATE FUNCTION mytext_eq_text (mytext, text) RETURNS boolean AS ...;
CREATE OPERATOR = (procedure=mytext_eq_text, leftarg=mytext, rightarg=text);
CREATE TABLE mytable (val mytext);

SELECT * FROM mytable WHERE val = 'foo';

Этот запрос не будет использовать пользовательский оператор. Парсер сначала проверит, есть ли оператор mytext = mytext (Step 2.a), которого нет; затем он рассмотрит базовый тип домена text и проверит, есть ли оператор text = text (Step 2.b), которого есть; поэтому он разрешает литерал типа unknown как text и использует оператор text = text. Единственный способ использовать пользовательский оператор - явно привести литерал:

SELECT * FROM mytable WHERE val = text 'foo';

чтобы оператор mytext = text был найден немедленно в соответствии с правилом точного совпадения. Если достигнуты правила наилучшего совпадения, они активно дискриминируют операторы на типах доменов. Если бы они этого не делали, такой оператор создал бы слишком много неоднозначных сбоев оператора, потому что правила приведения типов всегда рассматривают домен как приводимый к или от его базового типа, и поэтому оператор домена будет считаться используемым во всех тех же случаях, что и оператор с аналогичным именем на базовом типе.




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