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.
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.
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ção | Número da estratégia |
|---|---|
| menor que | 1 |
| menor que ou igual | 2 |
| igual | 3 |
| maior que ou igual | 4 |
| maior que | 5 |
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ção | Número da estratégia |
|---|---|
| igual | 1 |
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ção | Número da estratégia |
|---|---|
| inteiramente à esquerda | 1 |
| não se estende à direita | 2 |
| sobrepõe | 3 |
| não se estende à esquerda | 4 |
| inteiramente à direita | 5 |
| idêntico (o mesmo) | 6 |
| contém | 7 |
| contido por | 8 |
| não se estende acima | 9 |
| inteiramente abaixo | 10 |
| inteiramente acima | 11 |
| não se estende abaixo | 12 |
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ção | Número da estratégia |
|---|---|
| inteiramente à esquerda | 1 |
| inteiramente à direita | 5 |
| idêntico (o mesmo) | 6 |
| contido por | 8 |
| inteiramente abaixo | 10 |
| inteiramente acima | 11 |
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ção | Número da estratégia |
|---|---|
| sobrepõe | 1 |
| contém | 2 |
| é contido por | 3 |
| igual | 4 |
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ção | Número da estratégia |
|---|---|
| menor que | 1 |
| menor que ou igual | 2 |
| igual | 3 |
| maior que ou igual | 4 |
| maior que | 5 |
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.)
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ção | Nú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 |
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ção | Número de suporte |
|---|---|
Calcula o valor de hash de 32 bits para uma chave | 1 |
| 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ção | Descrição | Número de suporte |
|---|---|---|
consistent | determina se a chave satisfaz o qualificador da consulta | 1 |
union | calcula a união de um conjunto de chaves | 2 |
compress | calcula a representação comprimida de uma chave ou valor a ser indexado (opcional) | 3 |
decompress | calcula a representação expandida de uma chave comprimida (opcional) | 4 |
penalty | calcula a penalidade para inserir nova chave na subárvore com a chave da subárvore dada | 5 |
picksplit | determina 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 resultantes | 6 |
same | compara duas chaves e retorna verdade se forem iguais | 7 |
distance | determina a distância da chave ao valor da consulta (opcional) | 8 |
fetch | calcula a representação original de uma chave comprimida para varreduras somente de índice (opcional) | 9 |
options | define opções específicas para esta classe de operador (opcional) | 10 |
sortsupport | fornece um comparador de classificação para ser usado em construções de índice rápidas (opcional) | 11 |
translate_cmptype | traduz 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ção | Descrição | Número de suporte |
|---|---|---|
config | fornece informações básicas sobre a classe de operador | 1 |
choose | determina como inserir um novo valor em uma tupla interna | 2 |
picksplit | determina como particionar um conjunto de valores | 3 |
inner_consistent | determina quais subpartições precisam ser procuradas para uma consulta | 4 |
leaf_consistent | determina se a chave satisfaz o qualificador da consulta | 5 |
options | define 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ção | Descrição | Nú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 |
extractValue | extrai chaves de um valor a ser indexado | 2 |
extractQuery | extrai chaves de uma condição de consulta | 3 |
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ção | Descrição | Número de suporte |
|---|---|---|
opcInfo | retorna informações internas descrevendo os dados resumidos das colunas indexadas | 1 |
add_value | adiciona novo valor a uma tupla de índice de resumo existente | 2 |
consistent | determina se o valor corresponde à condição da consulta | 3 |
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.
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:
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.
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.
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.
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.
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).
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 <->.
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.