36.16. Interligação de extensões a índices #

36.16.1. Métodos de índice e classes de operador
36.16.2. Estratégias de método de índice
36.16.3. Rotinas de suporte a método de índice
36.16.4. Exemplo
36.16.5. Classes de operador e famílias de operador
36.16.6. Dependências do sistema em relação às classes de operador
36.16.7. Operadores de ordenação
36.16.8. Funcionalidades especiais das classes de operador

Os procedimentos descritos até agora permitem definir novos tipos de dados, novas funções e novos operadores. Entretanto, ainda não se pode definir um índice em uma coluna do novo tipo de dados. Para fazer isto, deve-se definir uma classe de operador para o novo tipo de dados. Mais adiante nesta seção, este conceito será mostrado em um exemplo: uma nova classe de operador para o método de índice de Árvore-B que armazena e classifica números complexos em ordem crescente de valor absoluto.

As classes de operador podem ser agrupadas em famílias de operador para mostrar os relacionamentos entre classes semanticamente compatíveis. Quando apenas um único tipo de dados está envolvido, uma classe de operador é suficiente, portanto, vamos nos concentrar neste caso primeiro, e depois retornar às famílias de operador.

36.16.1. Métodos de índice e classes de operador #

As classes de operador estão associadas a um método de acesso a índice como, por exemplo: Árvore-B ou GIN. É possível definir um método de acesso a índice personalizado usando CREATE ACCESS METHOD. Veja Definição da interface do método de acesso a índice para obter mais informações.

As rotinas para um método de índice não sabem nada diretamente sobre os tipos de dados nos quais o método de índice irá operar. Em vez disso, uma classe de operador identifica o conjunto de operações que o método de índice precisa usar para trabalhar com um determinado tipo de dados. As classes de operador são assim chamadas, porque uma coisa que elas especificam é o conjunto de operadores da cláusula WHERE que podem ser usados com um índice (ou seja, podem ser convertidos em uma qualificação de varredura de índice). Uma classe de operador também pode especificar alguma função de suporte necessária para as operações internas do método de índice, mas que não corresponde diretamente a nenhum operador da cláusula WHERE que pode ser usado com o índice.

É possível definir várias classes de operador para o mesmo tipo de dados e método de índice. Ao fazer isto, vários conjuntos de semântica de indexação podem ser definidos para um único tipo de dados. Por exemplo, um índice de Árvore-B requer que uma ordem de classificação seja definida para cada tipo de dados em que trabalha. Pode ser útil para um tipo de dados de número complexo ter uma classe de operador de Árvore-B que classifica os dados por valor absoluto complexo, outra que classifica pela parte real, e assim por diante. Normalmente, uma das classes de operador será considerada mais útil, e será marcada como a classe de operador padrão para este tipo de dados e método de índice.

O mesmo nome de classe de operador pode ser usado para vários métodos de índice diferentes (por exemplo, os métodos de índice Árvore-B e hash têm classes de operador denominadas int4_ops), mas cada uma dessas classes é uma entidade independente, devendo ser definida em separado.

36.16.2. Estratégias de método de índice #

Os operadores associados a uma classe de operador são identificados pelo números de estratégia, que servem para identificar a semântica de cada operador no contexto de sua classe do operador. Por exemplo, as Árvores-B impõem uma ordem estrita nas chaves, de menor para maior, e assim operadores como menor que e maior que ou igual a são de interesse em relação a uma Árvore-B. Como o PostgreSQL permite que o usuário defina operadores, o PostgreSQL não pode olhar para o nome do operador (por exemplo, < ou >=), e dizer que tipo de comparação ele faz. Em vez disso, o método do índice define um conjunto de estratégias, que podem ser consideradas como operadores generalizados. Cada classe de operador especifica qual operador real corresponde a cada estratégia para um determinado tipo de dados, e a interpretação da semântica do índice.

O método do índice Árvore-B define cinco estratégias, mostradas na Tabela 36.3.

Tabela 36.3. Estratégias da Árvore-B

OperaçãoNúmero da estratégia
menor que1
menor que ou igual2
igual3
maior que ou igual4
maior que5

Os índices de hash dão suporte apenas a comparações de igualdade, portanto, usam apenas uma estratégia, mostrada na Tabela 36.4.

Tabela 36.4. Estratégias de hash

OperaçãoNúmero da estratégia
igual1

Os índices GiST são mais flexíveis: não possuem um conjunto fixo de estratégias. Em vez disso, a rotina de suporte de consistência de cada classe particular do operador GiST interpreta os números da estratégia como bem entender. Como exemplo, várias das classes de operador de índice GiST nativas indexam objetos geométricos bidimensionais, fornecendo as estratégias R-tree mostradas na Tabela 36.5. Quatro deles são verdadeiros testes bidimensionais (sobreposição, idêntico, contém, contido por); quatro consideram apenas a direção X; e os outros quatro fornecem os mesmos testes na direção Y.

Tabela 36.5. Estratégias R-tree para GiST bidimensional

OperaçãoNúmero da estratégia
inteiramente à esquerda1
não se estende à direita2
sobrepõe3
não se estende à esquerda4
inteiramente à direita5
idêntico (o mesmo)6
contém7
contido por8
não se estende acima9
inteiramente abaixo10
inteiramente acima11
não se estende abaixo12

Os índices SP-GiST são semelhantes aos índices GiST em flexibilidade: não possuem um conjunto fixo de estratégias. Em vez disso, as rotinas de suporte de cada classe de operador interpretam os números da estratégia conforme a definição da classe de operador. Como exemplo, os números de estratégia usados pelas classes de operador nativas para pontos são mostrados na Tabela 36.6.

Tabela 36.6. Estratégias de pontos SP-GiST

OperaçãoNúmero da estratégia
inteiramente à esquerda1
inteiramente à direita5
idêntico (o mesmo)6
contido por8
inteiramente abaixo10
inteiramente acima11

Os índices GIN são semelhantes aos índices GiST e SP-GiST, porque também não possuem um conjunto fixo de estratégias. Em vez disso, as rotinas de suporte de cada classe de operador interpretam os números da estratégia conforme a definição da classe de operador. Como exemplo, os números da estratégia usados pela classe de operador nativa para matrizes são mostrados na Tabela 36.7.

Tabela 36.7. Estratégias de matriz GIN

OperaçãoNúmero da estratégia
sobrepõe1
contém2
é contido por3
igual4

Os índices BRIN são semelhantes aos índices GiST, SP-GiST e GIN, porque também não possuem um conjunto fixo de estratégias. Em vez disso, as rotinas de suporte de cada classe de operador interpretam os números da estratégia conforme a definição da classe de operador. Como exemplo, os números de estratégia usados pelas classes de operador Minmax nativos são mostrados na Tabela 36.8.

Tabela 36.8. Estratégias Minmax para BRIN

OperaçãoNúmero da estratégia
menor que1
menor que ou igual2
igual3
maior que ou igual4
maior que5

Note que todos os operadores listados acima retornam valores booleanos. Na prática, todos os operadores definidos como operadores de procura de método de índice devem retornar o tipo de dados boolean, porque devem aparecer no nível superior da cláusula WHERE a ser usada com o índice. (Alguns métodos de acesso de índice também dão suporte a operadores de ordenação, que normalmente não retornam valores booleanos; este recurso é discutido em Operadores de ordenação.)

36.16.3. Rotinas de suporte a método de índice #

As estratégias geralmente não possuem informações suficientes para o sistema descobrir como usar o índice. Na prática, os métodos de índice requerem rotinas de suporte adicionais para funcionar. Por exemplo, o método de índice de Árvore-B deve conseguir comparar duas chaves e determinar se uma é maior, igual ou menor que a outra. Da mesma forma, o método de índice de hash deve conseguir calcular códigos de hash para valores de chave. Estas operações não correspondem a operadores usados em qualificações em comandos SQL; são rotinas administrativas utilizadas pelos métodos de indexação, internamente.

Assim como nas estratégias, a classe do operador identifica quais funções específicas devem desempenhar cada uma dessas funções para um determinado tipo de dados e interpretação semântica. O método de índice define o conjunto de funções de que necessita, e a classe de operador identifica as funções corretas a serem usadas, atribuindo-as aos números de funções de suporte especificados pelo método de índice.

Além disso, algumas opclasses permitem que os usuários especifiquem parâmetros que controlam seu comportamento. Cada método de acesso a índice nativo possui a função de suporte opcional options, que define o conjunto de parâmetros específicos da opclass.

As Árvores-B requerem uma função de suporte de comparação, e permitem que quatro funções de suporte adicionais sejam fornecidas por opção do autor da classe do operador, conforme mostrado na Tabela 36.9. Os requisitos para estas funções de suporte são explicados mais adiante em Seção 65.1.3.

Tabela 36.9. Funções de suporte para Árvore-B

FunçãoNúmero de suporte
Compara duas chaves e retorna um inteiro menor que zero, zero, ou maior que zero, indicando se a primeira chave é menor, igual, ou maior que a segunda 1
Retorna os endereços das funções de suporte de classificação, que podem ser chamadas em C (opcional) 2
Compara um valor de teste com um valor base, mais/menos um deslocamento, e retorna verdade ou falso conforme o resultado da comparação (opcional) 3
Determina se é seguro para índices que usam a classe de operador aplicar a otimização de deduplicação [a] das Árvores-B (opcional) 4
Define as opções específicas para esta classe de operador (opcional) 5
Retorna os endereços das funções de suporte a saltos que podem ser chamadas na linguagem C (opcional) 6

[a] Em computação, a deduplicação ou desduplicação de dados é uma técnica para eliminar cópias duplicadas de dados repetidos. Wikipédia (N. T.)


Os índices de hash requerem uma função de suporte, e permitem que sejam fornecidas duas funções adicionais a critério do autor da classe de operador, conforme mostrado na Tabela 36.10.

Tabela 36.10. Funções de suporte para hash

FunçãoNúmero de suporte
Calcula o valor de hash de 32 bits para uma chave1
Calcula o valor de hash de 64 bits para uma chave com um sal de 64 bits; se o valor do sal for 0, os 32 bits mais baixos do resultado devem corresponder ao valor que teria sido calculado pela função 1 (opcional) 2
Define as opções específicas para esta classe de operador (opcional) 3

Os índices GiST possuem doze funções de suporte, sete das quais são opcionais, conforme mostrado na Tabela 36.11. (Veja Seção 65.2 para obter mais informações.)

Tabela 36.11. Funções de suporte para GiST

FunçãoDescriçãoNúmero de suporte
consistentdetermina se a chave satisfaz o qualificador da consulta1
unioncalcula a união de um conjunto de chaves2
compresscalcula a representação comprimida de uma chave ou valor a ser indexado (opcional)3
decompresscalcula a representação expandida de uma chave comprimida (opcional)4
penaltycalcula a penalidade para inserir nova chave na subárvore com a chave da subárvore dada5
picksplitdetermina quais entradas de uma página devem ser movidas para a nova página, e calcula as chaves de união para as páginas resultantes6
samecompara duas chaves e retorna verdade se forem iguais7
distancedetermina a distância da chave ao valor da consulta (opcional)8
fetchcalcula a representação original de uma chave comprimida para varreduras somente de índice (opcional)9
optionsdefine opções específicas para esta classe de operador (opcional)10
sortsupportfornece um comparador de classificação para ser usado em construções de índice rápidas (opcional)11
translate_cmptypetraduz os tipos de comparação para os números de estratégia usados ​​pela classe do operador (opcional)12

Os índices SP-GiST têm seis funções de suporte, uma das quais é opcional, conforme mostrado na Tabela 36.12. Veja Seção 65.3 para obter mais informações.)

Tabela 36.12. Funções de suporte para SP-GiST

FunçãoDescriçãoNúmero de suporte
configfornece informações básicas sobre a classe de operador1
choosedetermina como inserir um novo valor em uma tupla interna2
picksplitdetermina como particionar um conjunto de valores3
inner_consistentdetermina quais subpartições precisam ser procuradas para uma consulta4
leaf_consistentdetermina se a chave satisfaz o qualificador da consulta5
optionsdefine opções específicas para esta classe de operador (opcional)6

Os índices GIN têm sete funções de suporte, quatro das quais são opcionais, conforme mostrado na Tabela 36.13. (Veja Índices GIN para obter mais informações.)

Tabela 36.13. Funções de suporte para GIN

FunçãoDescriçãoNúmero de suporte
compare compara duas chaves e retorna um inteiro menor que zero, zero, ou maior que zero, indicando se a primeira chave é menor, igual, ou maior que a segunda 1
extractValueextrai chaves de um valor a ser indexado2
extractQueryextrai chaves de uma condição de consulta3
consistent determina se o valor corresponde à condição de consulta (variante booleana) (opcional, se a função de suporte 6 estiver presente) 4
comparePartial compara a chave parcial da consulta e a chave do índice, e retorna um inteiro menor que zero, zero, ou maior que zero, indicando se o GIN deve ignorar esta entrada de índice, tratar a entrada como uma correspondência, ou interromper a varredura do índice (opcional) 5
triConsistent determina se o valor corresponde à condição de consulta (variante ternária) (opcional, se a função de suporte 4 estiver presente) 6
options define opções específicas para esta classe de operador (opcional) 7

Os índices BRIN possuem cinco funções básicas de suporte, sendo uma delas opcional, conforme mostrado na Tabela 36.14. Algumas versões das funções básicas requerem o fornecimento de funções de suporte adicionais. (Veja Seção 65.5.3 para obter mais informações.)

Tabela 36.14. Funções de suporte para BRIN

FunçãoDescriçãoNúmero de suporte
opcInfo retorna informações internas descrevendo os dados resumidos das colunas indexadas 1
add_valueadiciona novo valor a uma tupla de índice de resumo existente2
consistentdetermina se o valor corresponde à condição da consulta3
union calcula a união de duas tuplas de resumo 4
options define opções específicas para esta classe de operador (opcional) 5

Ao contrário dos operadores de procura, as funções de suporte retornam qualquer tipo de dados esperado pelo método de índice específico; por exemplo, no caso da função de comparação para Árvores-B, um inteiro com sinal. O número e os tipos de argumentos para cada função de suporte também dependem do método de índice. Para Árvore-B e hash, as funções de suporte de comparação e hash usam os mesmos tipos de dados de entrada que os operadores incluídos na classe de operador, mas este não é o caso da maioria das funções de suporte GiST, SP-GiST, GIN e BRIN.

36.16.4. Exemplo #

Agora que as ideias foram vistas, aqui está o prometido exemplo de criação de uma nova classe de operador. (Pode ser encontrada uma cópia de trabalho desse exemplo nos arquivos complex.sql e complex.c no diretório src/tutorial da distribuição do código-fonte.) A classe de operador encapsula operadores que classificam números complexos em ordem de valor absoluto, então foi escolhido o nome complex_abs_ops. Primeiro, é necessário um conjunto de operadores. O procedimento para definir operadores foi discutido em Operadores definidos pelo usuário. Para uma classe de operador em Árvore-B, os operadores necessários são:

  • valor absoluto menor que (estratégia 1)
  • valor absoluto menor que ou igual (estratégia 2)
  • valor absoluto igual (estratégia 3)
  • valor absoluto maior que ou igual (estratégia 4)
  • valor absoluto maior que (estratégia 5)

A maneira menos propensa a erros para definir um conjunto relacionado de operadores de comparação, é escrever a função de suporte de comparação de Árvore-B primeiro e, em seguida, escrever as outras funções como empacotadoras de uma linha em torno da função de suporte. Isto reduz as chances de obter resultados inconsistentes para casos extremos. Seguindo esta abordagem, primeiro é escrito:

#define Mag(c)  ((c)->x*(c)->x + (c)->y*(c)->y)

static int
complex_abs_cmp_internal(Complex *a, Complex *b)
{
    double      amag = Mag(a),
                bmag = Mag(b);

    if (amag < bmag)
        return -1;
    if (amag > bmag)
        return 1;
    return 0;
}

Agora a função menor que se parece com:

PG_FUNCTION_INFO_V1(complex_abs_lt);

Datum
complex_abs_lt(PG_FUNCTION_ARGS)
{
    Complex    *a = (Complex *) PG_GETARG_POINTER(0);
    Complex    *b = (Complex *) PG_GETARG_POINTER(1);

    PG_RETURN_BOOL(complex_abs_cmp_internal(a, b) < 0);
}

As outras quatro funções diferem apenas em como comparam o resultado da função interna com zero.

Em seguida, são declaradas as funções e os operadores baseados em funções para o SQL:

CREATE FUNCTION complex_abs_lt(complex, complex) RETURNS bool
    AS 'nome_do_arquivo', 'complex_abs_lt'
    LANGUAGE C IMMUTABLE STRICT;

CREATE OPERATOR < (
   leftarg = complex, rightarg = complex, procedure = complex_abs_lt,
   commutator = > , negator = >= ,
   restrict = scalarltsel, join = scalarltjoinsel
);

É importante especificar os operadores comutador e negador corretos, bem como as funções adequadas de restrição e seletividade de junção, caso contrário, o otimizador não conseguirá fazer uso efetivo do índice.

A seguir estão algumas outras coisas importantes a serem observadas:

  • Só pode haver um operador com o nome, digamos, =, e aceitando o tipo de dados complex para os dois operandos. Neste caso, não existe outro operador =, para complex, mas se tivesse sido construído um tipo de dados prático, provavelmente se desejaria que = fosse a operação de igualdade comum para números complexos (e não a igualdade dos valores absolutos). Assim sendo, seria necessário usar algum outro nome de operador para complex_abs_eq.

  • Embora o PostgreSQL possa lidar com funções com o mesmo nome SQL, desde que tenham diferentes tipos de dados de argumento, C só pode lidar com uma função global com um determinado nome. Portanto, não se deve dar nomes à função C como algo simples como abs_eq. Geralmente é uma boa prática incluir o nome do tipo de dados no nome da função C, para não entrar em conflito com funções para outros tipos de dados.

  • Poderíamos ter usado abs_eq como o nome SQL da função, deixando para o PostgreSQL distingui-la de qualquer outra função SQL com o mesmo nome pelos tipos de dados no argumento. Para manter o exemplo simples, é feito com que a função tenha os mesmos nomes no nível C e no nível SQL.

O próximo passo é o cadastramento da rotina de suporte exigida pelas Árvores-B. O código C de exemplo que implementa isto está no mesmo arquivo que contém as funções do operador. É assim que a função é declarada:

CREATE FUNCTION complex_abs_cmp(complex, complex)
    RETURNS integer
    AS 'nome_do_arquivo'
    LANGUAGE C IMMUTABLE STRICT;

Agora que temos os operadores necessários e a rotina de suporte, podemos finalmente criar a classe de operador:

CREATE OPERATOR CLASS complex_abs_ops
    DEFAULT FOR TYPE complex USING btree AS
        OPERATOR        1       < ,
        OPERATOR        2       <= ,
        OPERATOR        3       = ,
        OPERATOR        4       >= ,
        OPERATOR        5       > ,
        FUNCTION        1       complex_abs_cmp(complex, complex);

E está pronto! Agora deve ser possível criar e usar índices Árvores-B em colunas do tipo de dados complex.

Poderíamos ter escrito as entradas do operador de forma mais detalhada, como em

        OPERATOR        1       < (complex, complex) ,

mas não há necessidade de fazer isto quando os operadores usam o mesmo tipo de dados para o qual está sendo definida classe de operador.

O exemplo acima assume que se deseja tornar esta nova classe de operador a classe de operador padrão de Árvore-B para o tipo de dados complex. Se não é isto o que se deseja, deve ser deixada de fora a palavra DEFAULT.

36.16.5. Classes de operador e famílias de operador #

Até agora, se presumiu implicitamente que a classe de operador lida com apenas um tipo de dados. Embora certamente possa haver apenas um tipo de dados em uma determinada coluna de índice, é geralmente útil indexar operações que comparam uma coluna indexada com um valor de um tipo de dados diferente. Além disso, havendo uso para um operador de tipo de dados cruzados em conexão com uma classe de operador, geralmente o outro tipo de dados possui uma classe de operador relacionada própria. É útil tornar explícitas as conexões entre as classes relacionadas, porque isto pode ajudar o planejador a otimizar as consultas SQL (especialmente para classes de operador de Árvore-B, porque o planejador contém muito conhecimento sobre como trabalhar com eles).

Para lidar com estas necessidades, o PostgreSQL usa o conceito de família de operador. Uma família de operador contém uma ou mais classes de operador, e também pode conter operadores indexáveis e funções de suporte correspondentes que pertencem à família como um todo, mas não a uma única classe dentro da família. Diz-se que tais operadores e funções estão soltos dentro da família, ao contrário de estarem vinculados a uma classe específica. Normalmente, cada classe de operador contém operadores de tipo de dados único, enquanto os operadores de tipo de dados cruzados estão soltos na família.

Todos os operadores e funções em uma família de operador devem ter semântica compatível, onde os requisitos de compatibilidade são definidos pelo método de índice. Portanto, pode-se perguntar por que se preocupar em destacar subconjuntos específicos da família como classes de operador; e, de fato, para muitos propósitos, as divisões de classe são irrelevantes, e a família é o único agrupamento de interesse. A razão para definir classes de operador, é que elas especificam o quanto da família é necessário para suportar qualquer índice específico. Se houver um índice usando uma classe de operador, esta classe de operador não poderá ser eliminada sem eliminar o índice — mas outras partes da família de operador, ou seja, outras classes de operador e operadores soltos, podem ser descartados. Assim, uma classe de operador deve ser especificada para conter o conjunto mínimo de operadores e funções que são razoavelmente necessários para trabalhar com um índice em um tipo de dados específico e, em seguida, operadores relacionados, mas não essenciais, podem ser adicionados como membros soltos da família de operador.

Como exemplo, o PostgreSQL tem uma família de operador de Árvore-B integrada chamada integer_ops, que inclui as classes de operador int8_ops, int4_ops e int2_ops para índices nas colunas bigint (int8), integer (int4) e smallint (int2), respectivamente. A família também contém operadores de comparação de tipos de dados cruzados, permitindo que dois desses tipos de dados sejam comparados, de modo que um índice em um desses tipos de dados possa ser procurado usando um valor de comparação de outro tipo de dados. A família pode ser duplicada por estas definições:

CREATE OPERATOR FAMILY integer_ops USING btree;

CREATE OPERATOR CLASS int8_ops
DEFAULT FOR TYPE int8 USING btree FAMILY integer_ops AS
  -- standard int8 comparisons
  OPERATOR 1 < ,
  OPERATOR 2 <= ,
  OPERATOR 3 = ,
  OPERATOR 4 >= ,
  OPERATOR 5 > ,
  FUNCTION 1 btint8cmp(int8, int8) ,
  FUNCTION 2 btint8sortsupport(internal) ,
  FUNCTION 3 in_range(int8, int8, int8, boolean, boolean) ,
  FUNCTION 4 btequalimage(oid) ,
  FUNCTION 6 btint8skipsupport(internal) ;

CREATE OPERATOR CLASS int4_ops
DEFAULT FOR TYPE int4 USING btree FAMILY integer_ops AS
  -- standard int4 comparisons
  OPERATOR 1 < ,
  OPERATOR 2 <= ,
  OPERATOR 3 = ,
  OPERATOR 4 >= ,
  OPERATOR 5 > ,
  FUNCTION 1 btint4cmp(int4, int4) ,
  FUNCTION 2 btint4sortsupport(internal) ,
  FUNCTION 3 in_range(int4, int4, int4, boolean, boolean) ,
  FUNCTION 4 btequalimage(oid) ,
  FUNCTION 6 btint4skipsupport(internal) ;

CREATE OPERATOR CLASS int2_ops
DEFAULT FOR TYPE int2 USING btree FAMILY integer_ops AS
  -- standard int2 comparisons
  OPERATOR 1 < ,
  OPERATOR 2 <= ,
  OPERATOR 3 = ,
  OPERATOR 4 >= ,
  OPERATOR 5 > ,
  FUNCTION 1 btint2cmp(int2, int2) ,
  FUNCTION 2 btint2sortsupport(internal) ,
  FUNCTION 3 in_range(int2, int2, int2, boolean, boolean) ,
  FUNCTION 4 btequalimage(oid) ,
  FUNCTION 6 btint2skipsupport(internal) ;

ALTER OPERATOR FAMILY integer_ops USING btree ADD
  -- cross-type comparisons int8 vs int2
  OPERATOR 1 < (int8, int2) ,
  OPERATOR 2 <= (int8, int2) ,
  OPERATOR 3 = (int8, int2) ,
  OPERATOR 4 >= (int8, int2) ,
  OPERATOR 5 > (int8, int2) ,
  FUNCTION 1 btint82cmp(int8, int2) ,

  -- cross-type comparisons int8 vs int4
  OPERATOR 1 < (int8, int4) ,
  OPERATOR 2 <= (int8, int4) ,
  OPERATOR 3 = (int8, int4) ,
  OPERATOR 4 >= (int8, int4) ,
  OPERATOR 5 > (int8, int4) ,
  FUNCTION 1 btint84cmp(int8, int4) ,

  -- cross-type comparisons int4 vs int2
  OPERATOR 1 < (int4, int2) ,
  OPERATOR 2 <= (int4, int2) ,
  OPERATOR 3 = (int4, int2) ,
  OPERATOR 4 >= (int4, int2) ,
  OPERATOR 5 > (int4, int2) ,
  FUNCTION 1 btint42cmp(int4, int2) ,

  -- cross-type comparisons int4 vs int8
  OPERATOR 1 < (int4, int8) ,
  OPERATOR 2 <= (int4, int8) ,
  OPERATOR 3 = (int4, int8) ,
  OPERATOR 4 >= (int4, int8) ,
  OPERATOR 5 > (int4, int8) ,
  FUNCTION 1 btint48cmp(int4, int8) ,

  -- cross-type comparisons int2 vs int8
  OPERATOR 1 < (int2, int8) ,
  OPERATOR 2 <= (int2, int8) ,
  OPERATOR 3 = (int2, int8) ,
  OPERATOR 4 >= (int2, int8) ,
  OPERATOR 5 > (int2, int8) ,
  FUNCTION 1 btint28cmp(int2, int8) ,

  -- cross-type comparisons int2 vs int4
  OPERATOR 1 < (int2, int4) ,
  OPERATOR 2 <= (int2, int4) ,
  OPERATOR 3 = (int2, int4) ,
  OPERATOR 4 >= (int2, int4) ,
  OPERATOR 5 > (int2, int4) ,
  FUNCTION 1 btint24cmp(int2, int4) ,

  -- cross-type in_range functions
  FUNCTION 3 in_range(int4, int4, int8, boolean, boolean) ,
  FUNCTION 3 in_range(int4, int4, int2, boolean, boolean) ,
  FUNCTION 3 in_range(int2, int2, int8, boolean, boolean) ,
  FUNCTION 3 in_range(int2, int2, int4, boolean, boolean) ;

Note que esta definição sobrecarrega (overloads) a estratégia do operador e os números da função de suporte: cada número ocorre várias vezes dentro da família. Isto é permitido, desde que cada instância de um determinado número tenha tipos de dados de entrada distintos. As instâncias que possuem os dois tipos de dados de entrada iguais ao tipo de dados de entrada de uma classe de operador, são os operadores primários e funções de suporte para esta classe de operador e, na maioria dos casos, devem ser declarados como parte da classe de operador em vez de membros soltos da família.

Em uma família de operador de Árvore-B, todos os operadores da família devem ser classificados de forma compatível, conforme especificado em detalhes em Seção 65.1.2. Para cada operador da família deve haver uma função de suporte com os mesmos dois tipos de dados de entrada do operador. Recomenda-se que a família seja completa, ou seja, para cada combinação de tipos de dados, todos os operadores estão incluídos. Cada classe de operador deve incluir apenas os operadores não cruzados, e a função de suporte para seu tipo de dados.

Para construir uma família de operador de hash de vários tipos de dados, devem ser criadas funções de suporte de hash compatíveis para cada tipo de dados com suporte pela família. Aqui, compatibilidade significa que as funções têm a garantia de retornar o mesmo código hash para quaisquer dois valores considerados iguais pelos operadores de igualdade da família, mesmo quando os valores são de tipos diferentes. Isto é geralmente difícil de realizar quando os tipos têm representações físicas diferentes, mas pode ser feito em alguns casos. Além disso, converter o valor de um tipo de dados representado na família de operador para outro tipo de dados, também representado na família de operador por meio de uma conversão de coerção implícita ou binária, não deve alterar o valor de hash calculado. Note haver apenas uma função de suporte por tipo de dados, e não uma por operador de igualdade. Recomenda-se que uma família seja completa, ou seja, forneça um operador de igualdade para cada combinação de tipos de dados. Cada classe de operador deve incluir apenas o operador de igualdade de tipo de dados não cruzado, e a função de suporte para seu tipo de dados.

Os índices GiST, SP-GiST e GIN não têm nenhuma noção explícita de operações de tipo de dados cruzados. O conjunto de operadores com suporte é exatamente o que as funções de suporte primárias para uma determinada classe de operador podem manipular.

No BRIN, os requisitos dependem da estrutura que fornece as classes de operador. Para classes de operador baseadas em minmax, o comportamento necessário é o mesmo que para as famílias de operador de Árvore-B: todos os operadores da família devem classificar de forma compatível, e as conversões não devem alterar a ordem de classificação associada.

Nota

Antes do PostgreSQL 8.3, não existia o conceito de famílias de operador, portanto, quaisquer operadores de tipo de dados cruzados destinados a serem usados com um índice tinham que ser vinculados diretamente à classe de operador do índice. Embora esta abordagem ainda funcione, está em obsolescência, porque torna as dependências de um índice muito amplas, e porque o planejador pode lidar com comparações cruzadas de tipos de dados com mais eficiência quando os dois tipos de dados têm operadores na mesma família de operador.

36.16.6. Dependências do sistema em relação às classes de operador #

O PostgreSQL usa classes de operador para inferir as propriedades dos operadores em mais maneiras do que apenas verificar se podem ser usados com índices. Portanto, talvez se queira criar classes de operador, mesmo que não se tenha intenção de indexar nenhuma coluna desse tipo de dados.

Em particular, existem recursos SQL como ORDER BY e DISTINCT, que requerem comparação e classificação de valores. Para implementar estes recursos em um tipo de dados definido pelo usuário, o PostgreSQL procura a classe de operador Árvore-B padrão para o tipo de dados. O membro igual a dessa classe de operador define a noção do sistema de igualdade de valores para GROUP BY e DISTINCT, e a ordem de classificação imposta pela classe de operador define a ordem padrão para ORDER BY.

Se não houver classe de operador de Árvore-B padrão para o tipo de dados, o sistema irá procurar uma classe de operador de hash padrão. Mas como este tipo de classe de operador fornece apenas igualdade, ela só consegue dar suporte a agrupamento, e não a classificação.

Quando não há classe de operador padrão para um tipo de dados, são recebidos erros como não foi possível identificar um operador de ordenação se for tentado usar estes recursos SQL com o tipo de dados.

Nota

Nas versões do PostgreSQL anteriores à 7.4, as operações de classificação e agrupamento usariam implicitamente os operadores com os nomes =, <, e >. O novo comportamento de depender de classes de operador padrão evita ter que fazer qualquer suposição sobre o comportamento de operadores com nomes específicos.

A classificação por uma classe de operador de Árvore-B não-padrão é possível, especificando o operador menor que da classe em uma opção USING como, por exemplo

SELECT * FROM mytable ORDER BY somecol USING ~<~;

Como alternativa, especificar o operador maior que da classe em USING seleciona uma classificação de ordem decrescente.

A comparação de matrizes de um tipo de dados definido pelo usuário também conta com a semântica definida pela classe de operador de Árvore-B padrão do tipo de dados. Se não houver nenhuma classe de operador de Árvore-B padrão, mas houver uma classe de operador de hash padrão, a igualdade de matriz terá suporte, mas não as comparações de ordenação.

Outra funcionalidade SQL que requer ainda mais conhecimento específico do tipo de dados, é a opção de enquadramento RANGE deslocamento PRECEDING/FOLLOWING para funções de janela (veja Chamadas de função de janela). Para uma consulta como

SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING)
  FROM mytable;

não é suficiente saber ordenar por x; o banco de dados também deve saber como subtrair 5 ou adicionar 10 ao valor da linha atual de x para identificar os limites do quadro da janela atual. A comparação dos limites resultantes com os valores de x de outras linhas é possível usando os operadores de comparação fornecidos pela classe de operador Árvore-B que define a ordenação ORDER BY — mas os operadores de adição e subtração não fazem parte da classe de operador, então quais devem ser usados? A conexão permanente dessa escolha seria indesejável, porque diferentes ordens de classificação (diferentes classes de operador de Árvore-B) podem precisar de um comportamento diferente. Portanto, uma classe de operador de Árvore-B pode especificar uma função de suporte in_range que encapsula os comportamentos de adição e subtração que fazem sentido para sua ordem de classificação. Pode até ser fornecida mais de uma função de suporte in_range, caso haja mais de um tipo de dados que faça sentido usar como deslocamento nas cláusulas RANGE. Se a classe de operador Árvore-B associada à cláusula ORDER BY da janela não tiver uma função de suporte in_range correspondente, então a opção RANGE deslocamento PRECEDING/FOLLOWING não terá suporte.

Outro ponto importante é que um operador de igualdade que aparece em uma família de operador de hash é candidato para junções de hash, agregação de hash, e otimizações relacionadas. A família de operador de hash é essencial aqui, porque identifica a(s) função(ões) de hash a ser(em) usada(s).

36.16.7. Operadores de ordenação #

Alguns métodos de acesso de índice (atualmente, apenas GiST e SP-GiST) suportam o conceito de operadores de ordenação. O que discutimos até agora foram operadores de procura. Um operador de procura é aquele para o qual o índice pode ser procurado para encontrar todas as linhas que satisfaçam WHERE coluna_indexada operador constante. Note que nada é prometido sobre a ordem em que as linhas que correspondem serão retornadas. Em contraste, um operador de ordenação não restringe o conjunto de linhas que podem ser retornadas, mas determina sua ordem. Um operador de ordenação é aquele para o qual o índice pode ser verificado para retornar linhas na ordem representada por ORDER BY coluna_indexada operador constante. A razão para definir os operadores de ordenação dessa maneira, é que ela oferece suporte a procura de vizinho mais próximo, se o operador for aquele que mede a distância. Por exemplo, uma consulta como

SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;

encontra os dez locais mais próximos de um determinado ponto-alvo. Um índice GiST na coluna de local pode fazer isto de forma eficiente, porque <-> é um operador de ordenação.

Enquanto os operadores de procura precisam retornar resultados booleanos, os operadores de ordenação geralmente retornam algum outro tipo de dados, como ponto-flutuante ou numérico para distâncias. Este tipo de dados normalmente não é o mesmo que o tipo de dados que está sendo indexado. Para evitar suposições sobre o comportamento de diferentes tipos de dados, a definição de um operador de ordenação é necessária para dar nome a uma família de operador de Árvore-B que especifica a ordenação do tipo de dados de resultado. Como foi declarado na seção anterior, as famílias de operador de Árvore-B definem a noção de ordenação do PostgreSQL, então esta é uma representação natural. Como o operador de ponto <-> retorna float8, ele pode ser especificado em um comando de criação de classe de operador como este:

OPERATOR 15    <-> (point, point) FOR ORDER BY float_ops

onde float_ops é a família de operador nativa que inclui operações em float8. Esta declaração afirma que o índice consegue retornar linhas em ordem crescente de valores do operador <->.

36.16.8. Funcionalidades especiais das classes de operador #

Existem duas funcionalidades especiais de classes de operador que ainda não foram discutidas, principalmente porque não são úteis com os métodos de índice mais comumente usados.

Normalmente, declarar um operador como membro de uma classe (ou família) de operador significa que o método de índice pode recuperar exatamente o conjunto de linhas que satisfazem uma condição WHERE usando o operador. Por exemplo

SELECT * FROM tabela WHERE coluna_inteira < 4;

pode ser satisfeita exatamente por um índice de Árvore-B na coluna inteira. Mas há casos em que um índice é útil como um guia inexato para as linhas correspondentes. Por exemplo, se um índice GiST armazena apenas caixas delimitadoras para objetos geométricos, ele não pode satisfazer exatamente uma condição WHERE que testa a sobreposição entre objetos não retangulares, como polígonos. No entanto, poderíamos usar o índice para localizar objetos cuja caixa delimitadora se sobreponha à caixa delimitadora do objeto de destino e, em seguida, fazer o teste de sobreposição exata apenas nos objetos encontrados pelo índice. Se este cenário se aplicar, o índice é considerado com perdas (lossy) para o operador. As procuras de índice com perdas são implementadas fazendo com que o método de índice retorne o sinalizador recheck quando uma linha pode ou não satisfazer realmente a condição de consulta. O sistema principal testará a condição de consulta original na linha recuperada para ver se ela deve ser retornada como uma correspondência válida. Esta abordagem funciona se for garantido que o índice retornará todas as linhas necessárias, mais talvez algumas linhas adicionais, que podem ser eliminadas executando a chamada original do operador. Os métodos de índice que suportam procuras com perdas (atualmente, GiST, SP-GiST e GIN) permitem que as funções de suporte de classes de operador individuais definam o sinalizador de reverificação, portanto, este é essencialmente um recurso de classe de operador.

Considere novamente a situação onde está sendo armazenando no índice apenas a caixa delimitadora de um objeto complexo, como um polígono. Neste caso, não há muito valor em armazenar todo o polígono na entrada do índice — pode-se também armazenar apenas um objeto mais simples do tipo de dados box. Esta situação é expressa pela opção STORAGE em CREATE OPERATOR CLASS: seria escrito algo como:

CREATE OPERATOR CLASS polygon_ops
    DEFAULT FOR TYPE polygon USING gist AS
        ...
        STORAGE box;

Neste momento, apenas os métodos de índice GiST, SP-GiST, GIN e BRIN suportam um tipo de dados de STORAGE diferente do tipo de dados da coluna. As rotinas de suporte compress e decompress do GiST devem lidar com a conversão de tipo de dados quando STORAGE é usado. Da mesma forma, o SP-GiST requer uma função de suporte compress para converter para o tipo de dados de armazenamento, quando for diferente; se uma opclass do SP-GiST também suportar a recuperação de dados, a conversão reversa deve ser tratada pela função consistent. No GIN, o tipo de dados de STORAGE identifica o tipo de dados dos valores da chave, que normalmente é diferente do tipo de dados da coluna indexada — por exemplo, uma classe de operador para colunas de matriz inteira pode ter chaves que são apenas números inteiros. As rotinas de suporte GIN extractValue e extractQuery são responsáveis por extrair as chaves de valores indexados. BRIN é semelhante a GIN: o tipo de dados de STORAGE identifica o tipo de dados dos valores de resumo armazenados, e os procedimentos de suporte das classes de operador são responsáveis por interpretar os valores dos resumos corretamente.