63.1. Estructura básica de la API para índices #

Cada método de acceso a índices se describe mediante una fila en el catálogo del sistema pg_am. La entrada pg_am especifica un nombre y una función de controlador (handler function) para el método de acceso al índice. Estas entradas se pueden crear y eliminar mediante los comandos SQL CREATE ACCESS METHOD y DROP ACCESS METHOD.

La función controladora de un método de acceso a índices debe declararse para aceptar un único argumento de tipo internal y devolver el pseudotipo index_am_handler. El argumento es un valor ficticio que simplemente sirve para evitar que las funciones controladoras se llamen directamente desde comandos SQL. El resultado de la función debe ser una estructura asignada con palloc de tipo IndexAmRoutine, que contiene todo lo que el código central necesita saber para hacer uso del método de acceso al índice. La estructura IndexAmRoutine, también llamada estructura de la API del método de acceso, incluye campos que especifican diversas propiedades fijas del método de acceso, como por ejemplo si admite índices multicolumna. Más importante aún, contiene punteros a funciones de soporte para el método de acceso, que realizan todo el trabajo real para acceder a los índices. Estas funciones de soporte son funciones C puras y no son visibles ni invocables a nivel SQL. Las funciones de soporte se describen en Section 63.2.

La estructura IndexAmRoutine se define de la siguiente manera:

typedef struct IndexAmRoutine
{
    NodeTag     type;

    /*
     * Número total de estrategias (operadores) mediante las cuales podemos recorrer/buscar
     * este AM. Cero si el AM no tiene un conjunto fijo de asignaciones de estrategia.
     */
    uint16      amstrategies;
    /* número total de funciones de soporte que utiliza este AM */
    uint16      amsupport;
    /* número de función de soporte de opciones de opclass o 0 */
    uint16      amoptsprocnum;
    /* ¿admite el AM la ordenación mediante el valor de la columna indexada (ORDER BY)? */
    bool        amcanorder;
    /* ¿admite el AM ORDER BY por el resultado de un operador en la columna indexada? */
    bool        amcanorderbyop;
    /* ¿admite el AM el hashing utilizando una API coherente con el AM de hash? */
    bool        amcanhash;
    /* ¿tienen los operadores dentro de una opfamily una semántica de igualdad coherente? */
    bool        amconsistentequality;
    /* ¿tienen los operadores dentro de una opfamily una semántica de ordenación coherente? */
    bool        amconsistentordering;
    /* ¿admite el AM el escaneo hacia atrás? */
    bool        amcanbackward;
    /* ¿admite el AM índices UNIQUE? */
    bool        amcanunique;
    /* ¿admite el AM índices multicolumna? */
    bool        amcanmulticol;
    /* ¿requiere el AM que los escaneos tengan una restricción en la primera columna del índice? */
    bool        amoptionalkey;
    /* ¿maneja el AM condiciones ScalarArrayOpExpr? */
    bool        amsearcharray;
    /* ¿maneja el AM condiciones IS NULL/IS NOT NULL? */
    bool        amsearchnulls;
    /* ¿puede el tipo de datos de almacenamiento del índice diferir del tipo de datos de la columna? */
    bool        amstorage;
    /* ¿se puede agrupar (cluster) sobre un índice de este tipo? */
    bool        amclusterable;
    /* ¿maneja el AM los bloqueos de predicado? */
    bool        ampredlocks;
    /* ¿admite el AM el escaneo en paralelo? */
    bool        amcanparallel;
    /* ¿admite el AM la construcción en paralelo? */
    bool        amcanbuildparallel;
    /* ¿admite el AM columnas incluidas con la cláusula INCLUDE? */
    bool        amcaninclude;
    /* ¿utiliza el AM maintenance_work_mem? */
    bool        amusemaintenanceworkmem;
    /* ¿resume el AM las tuplas, resumiendo al menos todas las tuplas del bloque
     * en un solo resumen? */
    bool        amsummarizing;
    /* OR de los flags de vacuum en paralelo */
    uint8       amparallelvacuumoptions;
    /* tipo de datos almacenados en el índice, o InvalidOid si es variable */
    Oid         amkeytype;

    /* funciones de la interfaz */
    ambuild_function ambuild;
    ambuildempty_function ambuildempty;
    aminsert_function aminsert;
    aminsertcleanup_function aminsertcleanup;   /* puede ser NULL */
    ambulkdelete_function ambulkdelete;
    amvacuumcleanup_function amvacuumcleanup;
    amcanreturn_function amcanreturn;   /* puede ser NULL */
    amcostestimate_function amcostestimate;
    amgettreeheight_function amgettreeheight;   /* puede ser NULL */
    amoptions_function amoptions;
    amproperty_function amproperty;     /* puede ser NULL */
    ambuildphasename_function ambuildphasename;   /* puede ser NULL */
    amvalidate_function amvalidate;
    amadjustmembers_function amadjustmembers; /* puede ser NULL */
    ambeginscan_function ambeginscan;
    amrescan_function amrescan;
    amgettuple_function amgettuple;     /* puede ser NULL */
    amgetbitmap_function amgetbitmap;   /* puede ser NULL */
    amendscan_function amendscan;
    ammarkpos_function ammarkpos;       /* puede ser NULL */
    amrestrpos_function amrestrpos;     /* puede ser NULL */

    /* funciones de la interfaz para admitir escaneos de índice en paralelo */
    amestimateparallelscan_function amestimateparallelscan;    /* puede ser NULL */
    aminitparallelscan_function aminitparallelscan;    /* puede ser NULL */
    amparallelrescan_function amparallelrescan;    /* puede ser NULL */

    /* funciones de la interfaz para admitir la planificación */
    amtranslate_strategy_function amtranslatestrategy;  /* puede ser NULL */
    amtranslate_cmptype_function amtranslatecmptype;    /* puede ser NULL */
} IndexAmRoutine;

Para ser útil, un método de acceso a índices también debe tener una o más familias de operadores (operator families) y clases de operadores (operator classes) definidas en pg_opfamily, pg_opclass, pg_amop y pg_amproc. Estas entradas permiten al planificador determinar qué tipo de cualificaciones de consulta se pueden usar con los índices de este método de acceso. Las familias y clases de operadores se describen en Section 36.16, que es material prerrequisito para leer este capítulo.

Un índice individual se define mediante una entrada pg_class que lo describe como una relación física, más una entrada pg_index que muestra el contenido lógico del índice — es decir, el conjunto de columnas de índice que tiene y la semántica de esas columnas, tal como se captura en las clases de operadores asociadas. Las columnas del índice (valores clave) pueden ser columnas simples de la tabla subyacente o expresiones sobre las filas de la tabla. El método de acceso a índices normalmente no tiene interés en el origen de los valores clave del índice (siempre se le entregan valores clave precalculados), pero estará muy interesado en la información de la clase de operadores en pg_index. Se puede acceder a ambas entradas de catálogo como parte de la estructura de datos Relation que se pasa a todas las operaciones sobre el índice.

Algunos de los campos de banderas (flags) de IndexAmRoutine tienen implicaciones que no son obvias. Los requisitos de amcanunique se analizan en Section 63.5. La bandera amcanmulticol afirma que el método de acceso admite índices de múltiples columnas clave, mientras que amoptionalkey afirma que permite escaneos donde no se proporciona ninguna cláusula de restricción indexable para la primera columna del índice. Cuando amcanmulticol es falso, amoptionalkey dice esencialmente si el método de acceso admite escaneos de índice completo sin ninguna cláusula de restricción. Los métodos de acceso que admiten varias columnas de índice deben admitir escaneos que omitan las restricciones en cualquiera o todas las columnas posteriores a la primera; sin embargo, se les permite exigir que aparezca alguna restricción para la primera columna del índice, y esto se señala estableciendo amoptionalkey en falso. Una razón por la que un AM de índice podría establecer amoptionalkey en falso es si no indexa valores nulos. Dado que la mayoría de los operadores indexables son estrictos (strict) y, por lo tanto, no pueden devolver true para entradas nulas, a primera vista resulta atractivo no almacenar entradas de índice para valores nulos: nunca podrían ser devueltos por un escaneo de índice de todos modos. Sin embargo, este argumento falla cuando un escaneo de índice no tiene una cláusula de restricción para una columna de índice dada. En la práctica, esto significa que los índices que tienen amoptionalkey en verdadero deben indexar los nulos, ya que el planificador podría decidir utilizar dicho índice sin ninguna clave de escaneo en absoluto. Una restricción relacionada es que un método de acceso a índices que admite múltiples columnas de índice debe admitir la indexación de valores nulos en las columnas posteriores a la primera, porque el planificador asumirá que el índice se puede utilizar para consultas que no restringen estas columnas. Por ejemplo, considera un índice sobre (a,b) y una consulta con WHERE a = 4. El sistema asumirá que el índice se puede utilizar para buscar filas con a = 4, lo cual es incorrecto si el índice omite las filas donde b es nulo. Sin embargo, es aceptable omitir las filas donde la primera columna indexada es nula. Un método de acceso a índices que sí indexa nulos también puede establecer amsearchnulls, indicando que admite cláusulas IS NULL e IS NOT NULL como condiciones de búsqueda.

La bandera amcaninclude indica si el método de acceso admite columnas incluidas (included), es decir, si puede almacenar (sin procesar) columnas adicionales más allá de las columnas clave. Los requisitos del párrafo anterior se aplican únicamente a las columnas clave. En particular, la combinación de amcanmulticol=false y amcaninclude=true es sensata: significa que solo puede haber una columna clave, pero también puede haber columnas incluidas. Además, se debe permitir que las columnas incluidas sean nulas, independientemente de amoptionalkey.

La bandera amsummarizing indica si el método de acceso resume las tuplas indexadas, con una granularidad de resumen de al menos por bloque. Los métodos de acceso que no apuntan a tuplas individuales, sino a rangos de bloques (como BRIN), pueden permitir que la optimización HOT continúe. Esto no se aplica a los atributos a los que se hace referencia en los predicados del índice; una actualización de dicho atributo siempre desactiva HOT.