Los procedimientos descritos hasta aqu� le permiten definir un nuevo tipo, nuevas funciones y nuevos operadores. Sin embargo, todav�a no podemos definir un �ndice secundario (tal como un B-tree, R-tree o m�todo de acceso hash) sobre un nuevo tipo o sus operadores.
M�rese nuevamente El principal sistema de catalogo de Postgres. La mitad derecha muestra los cat�logos que debemos modificar para poder indicar a Postgres c�mo utilizar un tipo definido por el usuario y/u operadores definidos por el usuario con un �ndice (es decir, pg_am, pg_amop, pg_amproc, pg_operator y pg_opclass). Desafortunadamente, no existe un comando simple para hacer esto. Demostraremos c�mo modificar estos cat�logos a trav�s de un ejemplo ejecutable: una nueva clase de operador para el m�todo de acceso B-tree que almacene y ordene n�meros complejos en orden ascendente de valor absoluto.
La clase pg_am contiene una instancia para cada m�dodo de acceso definido por el usuario. El soporte de acceso a la memoria (heap) est� integrado en Postgres, pero todos los dem�s m�todos de acceso est�n descritos aqu�. El esquema es
Tabla 1. Esquema de un �ndice
Atributo | Descripci�n |
---|---|
amname | nombre del m�todo de acceso |
amowner | identificador de objeto del propietario de esta instancia en pg_user |
amstrategies | n�mero de estrategias para este m�todo de acceso (v�ase m�s abajo) |
amsupport | n�mero de rutinas de soporte para este m�todo de acceso (v�ase m�s abajo) |
amorderstrategy | cero si el �ndice no ofrece secuencia de ordenamiento, sino el n�mero de estrategia del operador de estrategia que describe la secuencia de ordenamiento |
amgettuple | |
aminsert | |
... | indicadores de procedimiento para las rutinas de interfaz con el m�todo de acceso. Por ejemplo, aqu� aparecen identificadores regproc para abrir, cerrar y obtener instancias desde el m�todo de acceso |
El identificador de objeto (object ID) de la instancia en pg_am se utiliza como una clave for�nea en multitud de otras clases. No es necesario que Ud. agregue una nueva instancia en esta clase; lo que debe interesarle es el identificador de objeto (object ID) de la instancia del m�todo de acceso que quiere extender:
SELECT oid FROM pg_am WHERE amname = 'btree'; +----+ |oid | +----+ |403 | +----+ |
El atributo amstrategies tiene como finalidad estandarizar comparaciones entre tipos de datos. Por ejemplo, los B-trees imponen un ordenamiento estricto en las claves, de menor a mayor. Como Postgres permite al usuario definir operadores, no puede, a trav�s del nombre del operador (por ej., ">" or "<"), identificar qu� tipo de comparaci�n es. De hecho, algunos m�todos de acceso no imponen ning�n ordenamiento. Por ejemplo, los R-trees expresan una relaci�n de inclusi�n en un rect�ngulo, mientras que una estructura de datos de tipo hash expresa �nicamente similaridad de bits basada en el valor de una funci�n hash. Postgres necesita alguna forma consistente para interpretar los requisitos en sus consultas, identificando el operador y decidiendo si se puede utilizar un �ndice existente. Esto implica que Postgres necesita conocer, por ejemplo, que los operadores "<=" y ">" particionan un B-tree. Postgres utiliza estrategias para expresar esas relaciones entre los operadores y las formas en que pueden utilizarse al recorrer los �ndices.
Definir un nuevo conjunto de estrategias est� m�s all� del alcance de esta exposici�n, pero explicaremos c�mo funcionan las estrategias B-tree porque necesitar� conocerlas para agregar una nueva clase de operador. En la clase pg_am, el atributo amstrategies es el n�mero de estrategias definidas para este m�todo de acceso. Para los B-trees, este n�mero es 5. Estas estrategias corresponden a
Tabla 2. Estrategias B-tree
Operaci�n | �ndice |
---|---|
menor que | 1 |
menor que o igual a | 2 |
igual | 3 |
mayor que o igual a | 4 |
mayor que | 5 |
La idea es que ser� necesario agregar procedimientos correspondientes a las comparaciones mencionadas arriba a la tabla pg_amop (v�ase m�s abajo). El c�digo de m�todo de acceso puede utilizar estos n�meros de estrategia, sin tener en cuenta el tipo de datos, para resolver c�mo particionar el B-tree, calcular la selectividad, etc�tera. No se preocupe a�n acerca de los detalles para agregar procedimientos; s�lo comprenda que debe existir un conjunto de procedimientos para int2, int4, oid, y todos los dem�s tipos de datos donde puede operar un B-tree.
Algunas veces, las estrategias no proporcionan la informaci�n suficiente para resolver la forma de utilizar un �ndice. Algunos m�todos de acceso requieren otras rutinas de soporte para poder funcionar. Por ejemplo, el m�todo de acceso B-tree debe ser capaz de comparar dos claves y determinar si una es mayor que, igual a, o menor que la otra. De manera an�loga, el m�todo de acceso R-tree debe ser capaz de calcular intersecciones, uniones, y tama�os de rect�ngulos. Estas operaciones no corresponden a requisitos del usuario en las consultas SQL; son rutinas administrativas utilizadas por los m�todos de acceso, internamente.
Para manejar diversas rutinas de soporte consistentemente entre todos los m�todos de acceso de Postgres, pg_am incluye un atributo llamado amsupport. Este atributo almacena el n�mero de rutinas de soporte utilizadas por un m�todo de acceso. Para los B-trees, este n�mero es uno -- la rutina que toma dos claves y devuelve -1, 0, o +1, dependiendo si la primer clave es menor que, igual a, o mayor que la segunda.
En t�rminos estrictos, esta rutina puede devolver un n�mero negativo (< 0), 0, o un valor positivo distinto de cero (> 0). |
La entrada amstrategies en pg_am s�lo indica el n�mero de estrategias definidas para el m�todo de acceso en cuesti�n. Los procedimientos para menor que, menor que o igual a, etc�tera no aparecen en pg_am. De manera similar, amsupport es solamente el n�mero de rutinas de soporte que requiere el m�todo de acceso. Las rutinas reales est�n listadas en otro lado.
Adem�s, la entrada amorderstrategy indica si el m�todo de acceso soporta o no un recorrido ordenado. Cero significa que no; si lo hace, amorderstrategy es el n�mero de la rutina de estrategia que corresponde al operador de ordenamiento. Por ejemplo, btree tiene amorderstrategy = 1 que corresponde al n�mero de estrategia de "menor que".
La pr�xima clase de inter�s es pg_opclass. Esta clase tiene como �nica finalidad asociar un nombre y tipo por defecto con un oid. En pg_amop cada clase de operador B-tree tiene un conjunto de procedimientos, de uno a cinco, descritos m�s arriba. Algunas clases de operadores (opclasses) son int2_ops,int4_ops, y oid_ops. Es necesario que Ud. agregue una instancia con su nombre de clase de operador (por ejemplo, complex_abs_ops) a pg_opclass. El oid de esta instancia es una clave for�nea en otras clases.
INSERT INTO pg_opclass (opcname, opcdeftype) SELECT 'complex_abs_ops', oid FROM pg_type WHERE typname = 'complex_abs'; SELECT oid, opcname, opcdeftype FROM pg_opclass WHERE opcname = 'complex_abs_ops'; +------+-----------------+------------+ |oid | opcname | opcdeftype | +------+-----------------+------------+ |17314 | complex_abs_ops | 29058 | +------+-----------------+------------+ |
De esta manera ahora tenemos un m�todo de acceso y una clase de operador. A�n necesitamos un conjunto de operadores; el procedimiento para definir operadores fue discutido antes en este manual. Para la clase de operador complex_abs_ops en Btrees, los operadores que necesitamos son:
valor absoluto menor que (absolute value less-than) valor absoluto menor que o igual a (absolute value less-than-or-equal) valor absoluto igual (absolute value equal) valor absoluto mayor que o igual a (absolute value greater-than-or-equal) valor absoluto mayor que (absolute value greater-than) |
Supongamos que el c�digo que implementa las funciones definidas est� almacenado en el archivo PGROOT/src/tutorial/complex.c
Parte del c�digo ser� parecido a este: (n�tese que solamente mostraremos el operador de igualdad en el resto de los ejemplos. Los otros cuatro operadores son muy similares. Refi�rase a complex.co complex.source para m�s detalles.)
#define Mag(c) ((c)->x*(c)->x + (c)->y*(c)->y) bool complex_abs_eq(Complex *a, Complex *b) { double amag = Mag(a), bmag = Mag(b); return (amag==bmag); } |
Hay un par de cosas importantes que suceden arriba.
Primero, n�tese que se est�n definiendo operadores menor que, menor que o igual a, igual, mayor que o igual a, y mayor que para int4. Todos estos operadores ya est�n definidos para int4 bajo los nombres <, <=, =, >=, and >. Los nuevos operadores, por supuesto, se comportan de manera distinta. Para garantizar que Postgres usa estos nuevos operadores en vez de los anteriores, es necesario que sean nombrados distinto que ellos. Este es un punto clave: Ud. puede sobrecargar operadores en Postgres, pero s�lo si el operador no ha sido definido a�n para los tipos de los argumentos. Es decir, si Ud. tiene < definido para (int4, int4), no puede definirlo nuevamente. Postgres no comprueba esto cuando define un nuevo operador, as� es que debe ser cuidadoso. Para evitar este problema, se utilizar�n nombres dispares para los operadores. Si hace esto mal, los m�todos de acceso seguramente fallen cuando intente hacer recorridos.
El otro punto importante es que todas las funciones de operador devuelven valores l�gicos (Boolean). Los m�todos de acceso cuentan con este hecho. (Por otro lado, las funciones de soporte devuelven cualquier cosa que el m�todo de acceso particular espera -- en este caso, un entero con signo.) La rutina final en el archivo es la "rutina de soporte" mencionada cuando trat�bamos el atributo amsupport de la clase pg_am. Utilizaremos esto m�s adelante. Por ahora, ign�relo.
CREATE FUNCTION complex_abs_eq(complex_abs, complex_abs) RETURNS bool AS 'PGROOT/tutorial/obj/complex.so' LANGUAGE 'c'; |
Ahora defina los operadores que los utilizar�n. Como se hizo notar, los nombres de operadores deben ser �nicos entre todos los operadores que toman dos operandos int4. Para ver si los nombres de operadores listados m�s arriba ya han sido ocupados, podemos hacer una consulta sobre pg_operator:
/* * esta consulta utiliza el operador de expresi�n regular (~) * para encontrar nombres de operadores de tres caracteres que terminen * con el car�cter & */ SELECT * FROM pg_operator WHERE oprname ~ '^..&$'::text; |
para ver si su nombre ya ha sido ocupado para los tipos que Ud. quiere. Las cosas importantes aqu� son los procedimientos (que son las funciones Cdefinidas m�s arriba) y las funciones de restricci�n y de selectividad de uni�n. Ud. deber�a utilizar solamente las que se usan abajo -- n�tese que hay distintas funciones para los casos menor que, igual, y mayor que. �stas deben proporcionarse, o el m�todo de acceso fallar� cuando intente utilizar el operador. Deber�a copiar los nombres para las funciones de restricci�n y de uni�n, pero utilice los nombres de procedimiento que defini� en el �ltimo paso.
CREATE OPERATOR = ( leftarg = complex_abs, rightarg = complex_abs, procedure = complex_abs_eq, restrict = eqsel, join = eqjoinsel ) |
T�ngase en cuenta que se definen cinco operadores correspondientes a menor, menor o igual, igual, mayor, y mayor o igual.
Ya casi hemos terminado. La �ltima cosa que necesitamos hacer es actualizar la tabla pg_amop. Para hacer esto, necesitamos los siguientes atributos:
Tabla 3. Esquema de pg_amproc
Atributo | Descripci�n |
---|---|
amopid | el oid de la instancia de pg_am para B-tree (== 403, v�ase arriba) |
amopclaid | el oid de la instancia de pg_opclass para complex_abs_ops(== lo que obtuvo en vez de 17314, v�ase arriba) |
amopopr | los oids de los operadores para la clase de operador (opclass) (que obtendremos dentro de un minuto) |
Entonces necesitamos los oids de los operadores que acabamos de definir. Buscaremos los nombres de todos los operadores que toman dos argumentos de tipo complex, y as� sacaremos los nuestros:
SELECT o.oid AS opoid, o.oprname INTO TABLE complex_ops_tmp FROM pg_operator o, pg_type t WHERE o.oprleft = t.oid and o.oprright = t.oid and t.typname = 'complex_abs'; +------+---------+ |oid | oprname | +------+---------+ |17321 | < | +------+---------+ |17322 | <= | +------+---------+ |17323 | = | +------+---------+ |17324 | >= | +------+---------+ |17325 | > | +------+---------+ |
Ahora estamos listos para actualizar pg_amop con nuestra nueva clase de operador. La cosa m�s importante en toda esta explicaci�n es que los operadores est�n ordenados desde menor que hasta mayor que, en pg_amop. Agregamos las instancias necesarias:
INSERT INTO pg_amop (amopid, amopclaid, amopopr, amopstrategy) SELECT am.oid, opcl.oid, c.opoid, 1 FROM pg_am am, pg_opclass opcl, complex_abs_ops_tmp c WHERE amname = 'btree' AND opcname = 'complex_abs_ops' AND c.oprname = '<'; |
El pr�ximo paso es registrar la "rutina de soporte" previamente descrita en la explicaci�n de pg_am. El oid de esta rutina de soporte est� almacenada en la clase pg_amproc, cuya clave est� compuesta por el oid del m�todo de acceso y el oid de la clase de operador. Primero, necesitamos registrar la funci�n en Postgres (recuerde que pusimos el c�digo C que implementa esta rutina al final del archivo en el cual implementamos las rutinas del operador):
CREATE FUNCTION complex_abs_cmp(complex, complex) RETURNS int4 AS 'PGROOT/tutorial/obj/complex.so' LANGUAGE 'c'; SELECT oid, proname FROM pg_proc WHERE proname = 'complex_abs_cmp'; +------+-----------------+ |oid | proname | +------+-----------------+ |17328 | complex_abs_cmp | +------+-----------------+ |
INSERT INTO pg_amproc (amid, amopclaid, amproc, amprocnum) SELECT a.oid, b.oid, c.oid, 1 FROM pg_am a, pg_opclass b, pg_proc c WHERE a.amname = 'btree' AND b.opcname = 'complex_abs_ops' AND c.proname = 'complex_abs_cmp'; |
Ahora necesitamos agregar una estrategia de hash para permitir que el tipo sea indexado. Hacemos esto utilizando otro tipo en pg_am pero reutilizamos los mismos operadores.
INSERT INTO pg_amop (amopid, amopclaid, amopopr, amopstrategy) SELECT am.oid, opcl.oid, c.opoid, 1 FROM pg_am am, pg_opclass opcl, complex_abs_ops_tmp c WHERE amname = 'hash' AND opcname = 'complex_abs_ops' AND c.oprname = '='; |
Para utilizar este �ndice en una cl�usula WHERE, necesitamos modificar la clase pg_operator de la siguiente manera.
UPDATE pg_operator SET oprrest = 'eqsel'::regproc, oprjoin = 'eqjoinsel' WHERE oprname = '=' AND oprleft = oprright AND oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs'); UPDATE pg_operator SET oprrest = 'neqsel'::regproc, oprjoin = 'neqjoinsel' WHERE oprname = '' AND oprleft = oprright AND oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs'); UPDATE pg_operator SET oprrest = 'neqsel'::regproc, oprjoin = 'neqjoinsel' WHERE oprname = '' AND oprleft = oprright AND oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs'); UPDATE pg_operator SET oprrest = 'scalarltsel'::regproc, oprjoin = 'scalarltjoinsel' WHERE oprname = '<' AND oprleft = oprright AND oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs'); UPDATE pg_operator SET oprrest = 'scalarltsel'::regproc, oprjoin = 'scalarltjoinsel' WHERE oprname = '<=' AND oprleft = oprright AND oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs'); UPDATE pg_operator SET oprrest = 'scalargtsel'::regproc, oprjoin = 'scalargtjoinsel' WHERE oprname = '>' AND oprleft = oprright AND oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs'); UPDATE pg_operator SET oprrest = 'scalargtsel'::regproc, oprjoin = 'scalargtjoinsel' WHERE oprname = '>=' AND oprleft = oprright AND oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs'); |
Y por �ltimo (�por fin!) registramos una descripci�n de este tipo.
INSERT INTO pg_description (objoid, description) SELECT oid, 'Two part G/L account' FROM pg_type WHERE typname = 'complex_abs'; |