63.2. Funciones del método de acceso a índices #

Las funciones de construcción y mantenimiento de índices que un método de acceso a índices debe proporcionar en IndexAmRoutine son:

IndexBuildResult *
ambuild (Relation heapRelation,
         Relation indexRelation,
         IndexInfo *indexInfo);

Construye un índice nuevo. La relación del índice se ha creado físicamente, pero está vacía. Debe rellenarse con los datos fijos que requiera el método de acceso, más las entradas para todas las tuplas que ya existan en la tabla. Normalmente, la función ambuild llamará a table_index_build_scan() para escanear la tabla en busca de tuplas existentes y calcular las claves que deben insertarse en el índice. La función debe devolver una estructura asignada con palloc que contenga estadísticas sobre el nuevo índice. La bandera amcanbuildparallel indica si el método de acceso admite construcciones de índices en paralelo. Cuando se establece en true, el sistema intentará asignar trabajadores paralelos para la construcción. Los métodos de acceso que solo admiten construcciones de índices no paralelas deben dejar esta bandera en false.

void
ambuildempty (Relation indexRelation);

Construye un índice vacío y lo escribe en la bifurcación de inicialización (INIT_FORKNUM) de la relación dada. Este método solo se llama para índices no registrados (unlogged); el índice vacío escrito en la bifurcación de inicialización se copiará sobre la bifurcación principal de la relación en cada reinicio del servidor.

bool
aminsert (Relation indexRelation,
          Datum *values,
          bool *isnull,
          ItemPointer heap_tid,
          Relation heapRelation,
          IndexUniqueCheck checkUnique,
          bool indexUnchanged,
          IndexInfo *indexInfo);

Inserta una tupla nueva en un índice existente. Los arreglos values e isnull proporcionan los valores de clave que se van a indexar, y heap_tid es el TID que se va a indexar. Si el método de acceso admite índices únicos (su bandera amcanunique es verdadera), entonces checkUnique indica el tipo de comprobación de unicidad que se va a realizar. Esto varía dependiendo de si la restricción única es diferible; consulta Section 63.5 para obtener más detalles. Normalmente, el método de acceso solo necesita el parámetro heapRelation cuando realiza la comprobación de unicidad (ya que entonces tendrá que mirar en el montón para verificar la vigencia de la tupla).

El valor booleano indexUnchanged proporciona una pista sobre la naturaleza de la tupla que se va a indexar. Cuando es verdadero, la tupla es un duplicado de alguna tupla existente en el índice. La nueva tupla es una versión de tupla MVCC sucesora lógicamente sin cambios. Esto sucede cuando se produce un UPDATE que no modifica ninguna columna cubierta por el índice, pero que, sin embargo, requiere una nueva versión en el índice. El AM del índice puede usar esta pista para decidir aplicar la eliminación de índices de abajo hacia arriba (bottom-up index deletion) en partes del índice donde se acumulan muchas versiones de la misma fila lógica. Ten en cuenta que la actualización de una columna que no es clave o de una columna que solo aparece en un predicado de índice parcial no afecta al valor de indexUnchanged. El código central determina el valor indexUnchanged de cada tupla utilizando un enfoque de baja sobrecarga que permite tanto falsos positivos como falsos negativos. Los AM de índice no deben tratar indexUnchanged como una fuente de información autoritativa sobre la visibilidad o el control de versiones de las tuplas.

El valor de resultado booleano de la función es significativo solo cuando checkUnique es UNIQUE_CHECK_PARTIAL. In esta caso, un resultado verdadero significa que se sabe que la nueva entrada es única, mientras que falso significa que podría no ser única (y se debe programar una comprobación de unicidad diferida). Para otros casos, se recomienda un resultado constante falso.

Es posible que algunos índices no indexen todas las tuplas. Si la tupla no se va a indexar, aminsert simplemente debe regresar sin hacer nada.

Si el AM del índice desea almacenar datos en caché a lo largo de sucesivas inserciones de índice dentro de una sentencia SQL, puede asignar espacio en indexInfo->ii_Context y almacenar un puntero a los datos en indexInfo->ii_AmCache (que inicialmente será NULL). Si se tienen que liberar recursos distintos de la memoria después de las inserciones en el índice, se puede proporcionar aminsertcleanup, que se llamará antes de que se libere la memoria.

void
aminsertcleanup (Relation indexRelation,
                 IndexInfo *indexInfo);

Limpia el estado que se mantuvo a lo largo de sucesivas inserciones en indexInfo->ii_AmCache. Esto es útil si los datos requieren pasos de limpieza adicionales (por ejemplo, liberar búferes anclados) y liberar simplemente la memoria no es suficiente.

IndexBulkDeleteResult *
ambulkdelete (IndexVacuumInfo *info,
              IndexBulkDeleteResult *stats,
              IndexBulkDeleteCallback callback,
              void *callback_state);

Elimina tuplas del índice. Esta es una operación de eliminación masiva (bulk delete) que se pretende implementar escaneando todo el índice y comprobando cada entrada para ver si debe eliminarse. Se debe llamar a la función de callback pasada, con el estilo callback(TID, callback_state) devuelve bool, para determinar si alguna entrada de índice en particular, identificada por su TID referenciado, debe eliminarse. Debe devolver NULL o una estructura asignada con palloc que contenga estadísticas sobre los efectos de la operación de eliminación. Está bien devolver NULL si no se necesita pasar información a amvacuumcleanup.

Debido al límite de maintenance_work_mem, es posible que sea necesario llamar a ambulkdelete más de una vez cuando se deban eliminar muchas tuplas. El argumento stats es el resultado de la llamada anterior para este índice (es NULL para la primera llamada dentro de una operación VACUUM). Esto permite al AM acumular estadísticas a lo largo de toda la operación. Normalmente, ambulkdelete modificará y devolverá la misma estructura si el parámetro stats pasado no es nulo.

IndexBulkDeleteResult *
amvacuumcleanup (IndexVacuumInfo *info,
                 IndexBulkDeleteResult *stats);

Limpia después de una operación VACUUM (cero o más llamadas a ambulkdelete). Esto no tiene que hacer nada más que devolver las estadísticas del índice, pero podría realizar una limpieza masiva, como recuperar páginas de índice vacías. stats es lo que devolvió la última llamada a ambulkdelete, o NULL si no se llamó a ambulkdelete porque no se necesitaba eliminar ninguna tupla. Si el resultado no es NULL, debe ser una estructura asignada con palloc. Las estadísticas que contiene se usarán para actualizar pg_class y serán reportadas por VACUUM si se proporciona la opción VERBOSE. Está bien devolver NULL si el índice no cambió en absoluto durante la operación VACUUM, pero de lo contrario se deben devolver las estadísticas correctas.

También se llamará a amvacuumcleanup al finalizar una operación ANALYZE. En este caso, stats siempre es NULL y cualquier valor devuelto será ignorado. Este caso se puede distinguir comprobando info->analyze_only. Se recomienda que el método de acceso no haga nada más que la limpieza posterior a la inserción en dicha llamada, y eso solo en un proceso de trabajo de autovacuum.

bool
amcanreturn (Relation indexRelation, int attno);

Comprueba si el índice puede admitir escaneos de solo índice en la columna dada, devolviendo el valor indexado original de la columna. El número de atributo está basado en 1, es decir, el attno de la primera columna es 1. Devuelve true si es compatible, de lo contrario false. Esta función siempre debe devolver true para las columnas incluidas (si son compatibles), ya que no tiene mucho sentido una columna incluida que no se pueda recuperar. Si el método de acceso no admite escaneos de solo índice en absoluto, el campo amcanreturn en su estructura IndexAmRoutine se puede establecer en NULL.

void
amcostestimate (PlannerInfo *root,
                IndexPath *path,
                double loop_count,
                Cost *indexStartupCost,
                Cost *indexTotalCost,
                Selectivity *indexSelectivity,
                double *indexCorrelation,
                double *indexPages);

Estima los costos de un escaneo de índice. Esta función se describe completamente en Section 63.6, a continuación.

int
amgettreeheight (Relation rel);

Calcula la altura de un índice en forma de árbol. Esta información se suministra a la función amcostestimate en path->indexinfo->tree_height y se puede usar para apoyar la estimación de costos. El resultado no se usa en ningún otro lugar, por lo que esta función se puede usar en realidad para calcular cualquier tipo de datos (que quepan en un entero) sobre el índice que la función de estimación de costos desee saber. Si el cálculo es costoso, podría ser útil almacenar en caché el resultado como parte de RelationData.rd_amcache.

bytea *
amoptions (ArrayType *reloptions,
           bool validate);

Analiza y valida el arreglo reloptions para un índice. Esto solo se llama cuando existe un arreglo reloptions no nulo para el índice. reloptions es un arreglo de tipo text que contiene entradas de la forma nombre=valor. La función debe construir un valor bytea, que se copiará en el campo rd_options de la entrada relcache del índice. El contenido de los datos del valor bytea queda libre para que lo defina el método de acceso; la mayoría de los métodos de acceso estándar utilizan la estructura StdRdOptions. Cuando validate es verdadero, la función debe reportar un mensaje de error adecuado si alguna de las opciones no se reconoce o tiene valores inválidos; cuando validate es falso, las entradas inválidas deben ignorarse silenciosamente. (validate es falso al cargar opciones que ya están almacenadas en pg_catalog; solo se podría encontrar una entrada inválida si el método de acceso ha cambiado sus reglas para las opciones, y en ese caso es apropiado ignorar las entradas obsoletas). Está bien devolver NULL si se desea el comportamiento predeterminado.

bool
amproperty (Oid index_oid, int attno,
            IndexAMProperty prop, const char *propname,
            bool *res, bool *isnull);

El método amproperty permite a los métodos de acceso a índices anular el comportamiento predeterminado de pg_index_column_has_property y funciones relacionadas. Si el método de acceso no tiene ningún comportamiento especial para las consultas de propiedades del índice, el campo amproperty en su estructura IndexAmRoutine se puede establecer en NULL. De lo contrario, se llamará al método amproperty con index_oid y attno ambos en cero para las llamadas a pg_indexam_has_property, o con index_oid válido y attno en cero para las llamadas a pg_index_has_property, o con index_oid válido y attno mayor que cero para las llamadas a pg_index_column_has_property. prop es un valor enum que identifica la propiedad que se está probando, mientras que propname es la cadena de nombre de propiedad original. Si el código central no reconoce el nombre de la propiedad, entonces prop es AMPROP_UNKNOWN. Los métodos de acceso pueden definir nombres de propiedad personalizados comprobando la coincidencia de propname (usa pg_strcasecmp para la coincidencia, para mantener la coherencia con el código central); para los nombres conocidos por el código central, es mejor inspeccionar prop. Si el método amproperty devuelve true, entonces ha determinado el resultado de la prueba de la propiedad: debe establecer *res al valor booleano que se va a devolver, o establecer *isnull en true para devolver un NULL. (Ambas variables referenciadas se inicializan en false antes de la llamada). Si el método amproperty devuelve false, entonces el código central procederá con su lógica normal para determinar el resultado de la prueba de la propiedad.

Los métodos de acceso que admiten operadores de ordenación deben implementar la prueba de la propiedad AMPROP_DISTANCE_ORDERABLE, ya que el código central no sabe cómo hacerlo y devolverá NULL. También puede ser ventajoso implementar la prueba de AMPROP_RETURNABLE, si eso se puede hacer de manera más económica que abriendo el índice y llamando a amcanreturn, que es el comportamiento predeterminado del código central. El comportamiento predeterminado debería ser satisfactorio para todas las demás propiedades estándar.

char *
ambuildphasename (int64 phasenum);

Devuelve el nombre textual del número de fase de construcción dado. Los números de fase son aquellos reportados durante la construcción de un índice a través de la interfaz pgstat_progress_update_param. Los nombres de las fases se exponen luego en la vista pg_stat_progress_create_index.

bool
amvalidate (Oid opclassoid);

Valida las entradas del catálogo para la clase de operadores especificada, en la medida en que el método de acceso pueda hacerlo razonablemente. Por ejemplo, esto podría incluir comprobar que se proporcionan todas las funciones de soporte requeridas. La función amvalidate debe devolver falso si la opclass es inválida. Los problemas deben reportarse con mensajes de ereport, normalmente al nivel INFO.

void
amadjustmembers (Oid opfamilyoid,
                 Oid opclassoid,
                 List *operators,
                 List *functions);

Valida los nuevos miembros propuestos de operadores y funciones de una familia de operadores, en la medida en que el método de acceso pueda hacerlo razonablemente, y establece sus tipos de dependencia si el predeterminado no es satisfactorio. Esto se llama durante CREATE OPERATOR CLASS y durante ALTER OPERATOR FAMILY ADD; en este último caso, opclassoid es InvalidOid. Los argumentos de tipo List son listas de estructuras OpFamilyMember, tal como se definen en amapi.h. Las pruebas realizadas por esta función serán típicamente un subconjunto de las realizadas por amvalidate, ya que amadjustmembers no puede asumir que está viendo un conjunto completo de miembros. Por ejemplo, sería razonable comprobar la firma de una función de soporte, pero no comprobar si se proporcionan todas las funciones de soporte requeridas. Cualquier problema se puede reportar lanzando un error. Los campos relacionados con dependencias de las estructuras OpFamilyMember son inicializados por el código central para crear dependencias fuertes (hard dependencies) en la opclass si se trata de CREATE OPERATOR CLASS, o dependencias suaves (soft dependencies) en la opfamily si se trata de ALTER OPERATOR FAMILY ADD. amadjustmembers puede ajustar estos campos si algún otro comportamiento es más apropiado. Por ejemplo, GIN, GiST y SP-GiST siempre establecen los miembros operadores para que tengan dependencias suaves en la opfamily, ya que la conexión entre un operador y una opclass es relativamente débil en estos tipos de índices; por lo tanto, es razonable permitir que los miembros operadores se añadan y eliminen libremente. A las funciones de soporte opcionales también se les suele asignar dependencias suaves, de modo que puedan ser eliminadas si es necesario.

El propósito de un índice, por supuesto, es admitir escaneos de tuplas que coincidan con una condición WHERE indexable, a menudo llamada calificador (qualifier) o clave de escaneo (scan key). La semántica del escaneo de índices se describe más detalladamente en Section 63.3, a continuación. Un método de acceso a índices puede admitir escaneos de índice simples (plain), escaneos de índice de mapa de bits (bitmap), o ambos. Las funciones relacionadas con el escaneo que un método de acceso a índices debe o puede proporcionar son:

IndexScanDesc
ambeginscan (Relation indexRelation,
             int nkeys,
             int norderbys);

Se prepara para un escaneo de índice. Los parámetros nkeys y norderbys indican el número de cualificadores y operadores de ordenación que se utilizarán en el escaneo; estos pueden ser útiles para propósitos de asignación de espacio. Ten en cuenta que los valores reales de las claves de escaneo no se proporcionan todavía. El resultado debe ser una estructura asignada con palloc. Por razones de implementación, el método de acceso al índice debe crear esta estructura llamando a RelationGetIndexScan(). En la mayoría de los casos, ambeginscan hace poco más que realizar esa llamada y tal vez adquirir bloqueos; las partes interesantes del inicio del escaneo de índices se encuentran en amrescan.

void
amrescan (IndexScanDesc scan,
          ScanKey keys,
          int nkeys,
          ScanKey orderbys,
          int norderbys);

Inicia o reinicia un escaneo de índice, posiblemente con nuevas claves de escaneo. (Para reiniciar utilizando las claves pasadas anteriormente, se pasa NULL para keys y/o orderbys). Ten en cuenta que no está permitido que el número de claves o de operadores de ordenación sea mayor que lo que se pasó a ambeginscan. En la práctica, la función de reinicio se utiliza cuando una nueva tupla externa es seleccionada por una unión de bucle anidado (nested-loop join) y, por lo tanto, se necesita un nuevo valor de comparación de clave, pero la estructura de la clave de escaneo sigue siendo la misma.

bool
amgettuple (IndexScanDesc scan,
            ScanDirection direction);

Recupera la siguiente tupla en el escaneo dado, moviéndose en la dirección dada (hacia adelante o hacia atrás en el índice). Devuelve true si se obtuvo una tupla, false si no quedan tuplas coincidentes. En el caso verdadero, el TID de la tupla se almacena en la estructura scan. Ten en cuenta que el éxito significa solo que el índice contiene una entrada que coincide con las claves de escaneo, no que la tupla exista necesariamente todavía en el montón o que pase la prueba de instantánea (snapshot) del llamador. En caso de éxito, amgettuple también debe establecer scan->xs_recheck en true o false. False significa que es seguro que la entrada del índice coincide con las claves de escaneo. True significa que esto no es seguro, y las condiciones representadas por las claves de escaneo deben volverse a comprobar contra la tupla del montón después de recuperarla. Esta disposición admite operadores de índice con pérdidas (lossy). Ten en cuenta que la nueva comprobación se extenderá solo a las condiciones de escaneo; un predicado de índice parcial (si lo hay) nunca es vuelto a comprobar por los llamadores de amgettuple.

Si el índice admite escaneos de solo índice (es decir, amcanreturn devuelve true para cualquiera de sus columnas), entonces, en caso de éxito, el AM también debe comprobar scan->xs_want_itup, y si es verdadero, debe devolver los datos originalmente indexados para la entrada del índice. Las columnas para las que amcanreturn devuelve falso pueden devolverse como nulas. Los datos pueden devolverse en forma de un puntero IndexTuple almacenado en scan->xs_itup, con el descriptor de tupla scan->xs_itupdesc; o en forma de un puntero HeapTuple almacenado en scan->xs_hitup, con el descriptor de tupla scan->xs_hitupdesc. (Este último formato debe usarse al reconstruir datos que posiblemente no quepan en una estructura IndexTuple). En cualquier caso, la gestión de los datos referenciados por el puntero es responsabilidad del método de acceso. Los datos deben seguir siendo válidos al menos hasta la siguiente llamada a amgettuple, amrescan o amendscan para el escaneo.

La función amgettuple solo necesita proporcionarse si el método de acceso admite escaneos de índice simples. Si no es así, el campo amgettuple en su estructura IndexAmRoutine debe establecerse en NULL.

int64
amgetbitmap (IndexScanDesc scan,
             TIDBitmap *tbm);

Recupera todas las tuplas en el escaneo dado y las añade al TIDBitmap suministrado por el llamador (es decir, realiza una operación OR del conjunto de IDs de tuplas en el conjunto que ya está en el mapa de bits). Se devuelve el número de tuplas recuperadas (esto podría ser solo un recuento aproximado, por ejemplo, algunos AM no detectan duplicados). Al insertar los IDs de tupla en el mapa de bits, amgetbitmap puede indicar que se requiere volver a comprobar las condiciones de escaneo para IDs de tupla específicos. Esto es análogo al parámetro de salida xs_recheck de amgettuple. Nota: en la implementación actual, el soporte para esta característica se combina con el soporte para el almacenamiento con pérdidas del propio mapa de bits y, por lo tanto, los llamadores vuelven a comprobar tanto las condiciones de escaneo como el predicado del índice parcial (si lo hay) para las tuplas comprobables. Sin embargo, esto podría no ser siempre cierto. amgetbitmap y amgettuple no se pueden usar en el mismo escaneo de índice; también existen otras restricciones al usar amgetbitmap, como se explica en Section 63.3.

La función amgetbitmap solo necesita proporcionarse si el método de acceso admite escaneos de índice de mapa de bits. Si no es así, el campo amgetbitmap en su estructura IndexAmRoutine debe establecerse en NULL.

void
amendscan (IndexScanDesc scan);

Finaliza un escaneo y libera recursos. La estructura scan en sí no debe liberarse, pero los bloqueos o anclajes tomados internamente por el método de acceso deben liberarse, así como cualquier otra memoria asignada por ambeginscan y otras funciones relacionadas con el escaneo.

void
ammarkpos (IndexScanDesc scan);

Marca la posición actual del escaneo. El método de acceso solo necesita admitir una posición de escaneo recordada por escaneo.

La función ammarkpos solo necesita proporcionarse si el método de acceso admite escaneos ordenados. Si no es así, el campo ammarkpos en su estructura IndexAmRoutine se puede establecer en NULL.

void
amrestrpos (IndexScanDesc scan);

Restaura el escaneo a la posición marcada más recientemente.

La función amrestrpos solo necesita proporcionarse si el método de acceso admite escaneos ordenados. Si no es así, el campo amrestrpos en su estructura IndexAmRoutine se puede establecer en NULL.

Además de admitir escaneos de índice ordinarios, algunos tipos de índices pueden desear admitir escaneos de índice en paralelo, que permiten que múltiples backends cooperen para realizar un escaneo de índice. El método de acceso al índice debe organizar las cosas de modo que cada proceso cooperativo devuelva un subconjunto de las tuplas que se obtendrían mediante un escaneo de índice ordinario y no paralelo, pero de tal manera que la unión de esos subconjuntos sea igual al conjunto de tuplas que serían devueltas por un escaneo de índice ordinario y no paralelo. Además, aunque no es necesario que haya ninguna ordenación global de las tuplas devueltas por un escaneo en paralelo, la ordenación de ese subconjunto de tuplas devueltas dentro de cada backend cooperativo debe coincidir con la ordenación solicitada. Las siguientes funciones pueden implementarse para admitir escaneos de índice en paralelo:

Size
amestimateparallelscan (Relation indexRelation,
                        int nkeys,
                        int norderbys);

Estima y devuelve el número de bytes de memoria compartida dinámica que necesitará el método de acceso para realizar un escaneo en paralelo. (Este número es adicional, no en lugar de, la cantidad de espacio necesario para los datos independientes de la AM en ParallelIndexScanDescData).

Los parámetros nkeys y norderbys indican el número de cualificadores y operadores de ordenación que se utilizarán en el escaneo; los mismos valores se pasarán a amrescan. Ten en cuenta que los valores reales de las claves de escaneo no se proporcionan todavía.

No es necesario implementar esta función para los métodos de acceso que no admiten escaneos en paralelo o para los que el número de bytes adicionales de almacenamiento requeridos es cero.

void
aminitparallelscan (void *target);

Esta función se llamará para inicializar la memoria compartida dinámica al comienzo de un escaneo en paralelo. target apuntará a al menos el número de bytes devuelto previamente por amestimateparallelscan, y esta función puede usar esa cantidad de espacio para almacenar los datos que desee.

No es necesario implementar esta función para los métodos de acceso que no admiten escaneos en paralelo o en los casos en que el espacio de memoria compartida requerido no necesita inicialización.

void
amparallelrescan (IndexScanDesc scan);

Esta función, si se implementa, se llamará cuando deba reiniciarse un escaneo de índice en paralelo. Debe restablecer cualquier estado compartido configurado por aminitparallelscan de modo que el escaneo se reinicie desde el principio.

CompareType
amtranslatestrategy (StrategyNumber strategy, Oid opfamily, Oid opcintype);

StrategyNumber
amtranslatecmptype (CompareType cmptype, Oid opfamily, Oid opcintype);

Estas funciones, si se implementan, serán llamadas por el planificador y el ejecutor para convertir entre valores fijos de CompareType y los números de estrategia específicos utilizados por el método de acceso. Estas funciones pueden ser implementadas por métodos de acceso que implementan una funcionalidad similar a los métodos de acceso btree o hash incorporados y, al implementar estas traducciones, el sistema puede conocer la semántica de las operaciones del método de acceso y puede utilizarlas en lugar de los índices btree o hash en varios lugares. Si la funcionalidad del método de acceso no es similar a la de esos métodos de acceso incorporados, estas funciones no necesitan ser implementadas. Si las funciones no se implementan, el método de acceso será ignorado para ciertas decisiones del planificador y del ejecutor, pero por lo demás es completamente funcional.