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(,
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
TID, callback_state) devuelve boolamvacuumcleanup.
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.