在 IndexAmRoutine
中,索引访问方法必须提供的索引构建和维护函数如下:
IndexBuildResult * ambuild (Relation heapRelation, Relation indexRelation, IndexInfo *indexInfo);
构建一个索引。索引关系已物理创建,但为空。必须使用访问方法所需的所有固定数据填充它,再添加表中已有的所有元组的条目。通常,ambuild
函数将调用 table_index_build_scan()
来扫描现有元组的表并计算需要插入索引的键。函数必须返回一个分配的包含新索引统计信息的结构。 amcanbuildparallel
标志指示访问方法是否支持并行索引构建。当设置为 true
时,系统将尝试为构建分配并行工作人员。仅支持非并行索引构建的访问方法应将此标志设置为 false
。
void ambuildempty (Relation indexRelation);
构建一个空索引,并写入给定关系的初始化 fork (INIT_FORKNUM
)。该方法仅对未记录的索引调用;写入初始化 fork 的空索引将在每次服务器重新启动时被复制到主关系 fork 上。
bool aminsert (Relation indexRelation, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRelation, IndexUniqueCheck checkUnique, bool indexUnchanged, IndexInfo *indexInfo);
将新元组插入现有索引。 values
和 isnull
数组给出要编制索引的键值,heap_tid
是要编制索引的 TID。如果访问方法支持唯一索引(其 amcanunique
标志为真),则 checkUnique
指示要执行的唯一性检查类型。这根据唯一约束是否可延期而有所不同;见第 62.5 节以了解详情。通常,只有在执行唯一性检查时访问方法才需要 heapRelation
参数(那时它必须检查堆以验证元组生存性)。
布尔值 indexUnchanged
给出了关于要编入索引的元组本质的提示。当其为真时,元组是索引中某个现有元组的副本。新元组是一个逻辑上没有更改的后继 MVCC 元组版本。这会发生在 UPDATE
不修改索引所覆盖的任何列,但仍然需要在索引中提供新版本时。索引 AM 可能会使用此提示来决定对同一逻辑行有许多版本的索引的部分应用自底向上索引删除。注意,更新非关键列或仅出现在部分索引谓词中的列不会影响 indexUnchanged
的值。核心代码通过允许出现误报和漏报的低开销方法确定每个元组的 indexUnchanged
值。索引 AM 绝对不能将 indexUnchanged
视为有关元组可见性或版本控制的信息的权威来源。
仅当 checkUnique
为 UNIQUE_CHECK_PARTIAL
时,函数的布尔结果值才有意义。在这种情况下,真结果意味着已知新条目是唯一的,而假结果意味着它可能是非唯一的(必须安排延迟的唯一性检查)。对于其他情况,则建议使用常量假结果。
一些索引可能不编入索引中所有的元组。如果元组未编入索引,则 aminsert
应在不执行任何操作的情况下返回。
如果索引 AM 希望跨 SQL 语句中的连续索引插入缓存数据,则可以在 indexInfo->ii_Context
中分配空间,并将指向 indexInfo->ii_AmCache
中数据的指针存储在其中(最初为 NULL)。如果索引插入后必须释放内存之外的其他资源,则可以提供 aminsertcleanup
,这将在释放内存之前进行调用。
void aminsertcleanup (Relation indexRelation, IndexInfo *indexInfo);
清除 indexInfo->ii_AmCache
中跨连续插入维持的状态。如果数据需要额外的清除步骤(例如,释放固定的缓冲区),并且仅仅释放内存还不够时,这将很有用。
IndexBulkDeleteResult * ambulkdelete (IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state);
从索引中删除元组。这是一项计划通过扫描整个索引并检查每个条目以查看是否应将其删除来实现的 “批量删除” 操作。必须按照 callback(
的样式调用传入的 TID
, callback_state) returns boolcallback
函数,以确定是否应删除由引用的 TID 标识的特定索引条目。必须返回 NULL 或包含有关删除操作的效果的统计信息的已分配结构。如果没有信息需要传递给 amvacuumcleanup
则返回 NULL 即可。
由于有限的 maintenance_work_mem
,在删除许多元组时可能需要多次调用 ambulkdelete
。 stats
参数是对此索引上一次调用得到的 result (在 VACUUM
操作中的第一次调用时,它为 NULL)。这允许 AM 积累整个操作过程的统计信息。通常情况下,如果传入的 stats
不为空,ambulkdelete
将修改并返回同一 struct。
IndexBulkDeleteResult * amvacuumcleanup (IndexVacuumInfo *info, IndexBulkDeleteResult *stats);
在 VACUUM
操作(零次或多次 ambulkdelete
调用)之后进行清理。这不必做其他任何事情,只需返回索引统计信息即可,但它可能执行大规模清理,例如回收空的索引页面。 stats
是最后一个 ambulkdelete
调用返回的任何内容,或者如果未调用 ambulkdelete
(因为不需要删除任何元组)则为 NULL。如果结果不为 NULL,它必须是 palloc'd struct。它包含的统计信息将用于更新 pg_class
,并且如果给定了 VERBOSE
,则由 VACUUM
报告。如果在 VACUUM
操作期间索引根本没有更改,则返回 NULL 是可以的,但否则应该返回正确的统计信息。
amvacuumcleanup
还会在 ANALYZE
操作完成后调用。在这种情况下,stats
始终为 NULL,并且任何返回值都将被忽略。可以通过检查 info->analyze_only
来区分这种情况。建议访问方法在这样的调用中除了进行后插入清理之外不执行任何其他操作,並且仅在自动真空工作进程中执行。
bool amcanreturn (Relation indexRelation, int attno);
通过返回列的原始索引值,检查索引是否支持给定列上的仅索引扫描。属性编号从 1 开始,即第一列的 attno 为 1。如果支持,则返回 true,否则返回 false。此函数始终应该为已包括的列返回 true(如果支持的话),因为无法检索的已包含的列几乎没有意义。如果访问方法根本不支持仅索引扫描,则可以在其 IndexAmRoutine
struct 中将 amcanreturn
字段设置为 NULL。
void amcostestimate (PlannerInfo *root, IndexPath *path, double loop_count, Cost *indexStartupCost, Cost *indexTotalCost, Selectivity *indexSelectivity, double *indexCorrelation, double *indexPages);
估算索引扫描的成本。此函数在 第 62.6 节 中得到充分描述。
bytea * amoptions (ArrayType *reloptions, bool validate);
解析和验证索引的 reloptions 数组。仅在有非空 reloptions 数组存在于索引中时才会调用此操作。reloptions
是一个 text
数组,包含格式为 name
=
value
的条目。此功能应构建一个 bytea
值,该值将复制到索引的 relcache 条目的 rd_options
字段中。对 bytea
的数据内容的访问可以由访问方法定义;大多数标准访问方法使用结构 StdRdOptions
。当 validate
为 true 时,如果任何选项不可识别或具有无效的值,该功能应报告一个合适的错误消息;当 validate
为 false 时,应忽略无效的条目。(当加载已存储在 pg_catalog
中的选项时,validate
为 false;只有在访问方法更改其选项规则时才会找到无效条目,在这种情况下,忽略过期的条目是合适的。)如果需要默认行为,则可以返回 NULL。
bool amproperty (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, bool *res, bool *isnull);
amproperty
方法允许索引访问方法覆盖 pg_index_column_has_property
和相关函数的默认行为。如果访问方法对于索引属性查询没有任何特殊行为,则其 IndexAmRoutine
结构中的 amproperty
字段可以设置为 NULL。否则,amproperty
方法将被调用,其中 index_oid
和 attno
均为零用于 pg_indexam_has_property
调用,或者 index_oid
有效且 attno
为零用于 pg_index_has_property
调用,或者 index_oid
有效且 attno
大于零用于 pg_index_column_has_property
调用。prop
是一个枚举值,它标识正在测试的属性,而 propname
是原始属性名称字符串。如果核心代码无法识别属性名称,则 prop
为 AMPROP_UNKNOWN
。访问方法可以通过检查 propname
查找匹配项来定义自定义属性名称(使用 pg_strcasecmp
进行匹配,以与核心代码保持一致);对于核心代码已知的名称,最好检查 prop
。如果 amproperty
方法返回 true
,则它已确定属性测试结果:它必须将 *res
设置为要返回的布尔值,或将 *isnull
设置为 true
以返回一个 NULL。(这两个引用的变量在调用之前都初始化为 false
。)如果 amproperty
方法返回 false
,则核心代码将继续其正常逻辑来确定属性测试结果。
支持排序运算符的访问方法应该实现 AMPROP_DISTANCE_ORDERABLE
属性测试,因为核心代码不知道如何执行此操作,并将返回 NULL。如果可以通过比打开索引并调用 amcanreturn
(这是核心代码的默认行为)更经济的方式进行,则实现 AMPROP_RETURNABLE
测试也很有利。对于所有其他标准属性,默认行为应该是令人满意的。
char * ambuildphasename (int64 phasenum);
返回给定构建阶段编号的文本名称。阶段编号是通过 pgstat_progress_update_param
接口在索引构建期间报告的阶段编号。然后在 pg_stat_progress_create_index
视图中公开阶段名称。
bool amvalidate (Oid opclassoid);
验证指定操作符类的目录项,在访问方法可以合理地执行该操作的范围内。例如,可能包括测试是否提供了所有必需的支持函数。amvalidate
函数如果所用的操作符类无效,则必须返回 false。问题必须用 ereport
消息进行报告,通常在 INFO
级。
void amadjustmembers (Oid opfamilyoid, Oid opclassoid, List *operators, List *functions);
验证操作符系列中拟议的新操作符和函数成员,在访问方法可以合理地执行该操作的范围内,如果默认设置不令人满意,则设置其依赖类型。将在 CREATE OPERATOR CLASS
和 ALTER OPERATOR FAMILY ADD
期间调用;在后者中,opclassoid
是 InvalidOid
。List
参数是 amapi.h
中定义的 OpFamilyMember
结构的列表。此函数执行的测试通常是 amvalidate
执行的测试的子集,因为 amadjustmembers
无法假设它看到一组完整的成员。例如,检查支持函数的签名是合理的,但检查是否提供了所有必需的支持函数是不合理的。可以通过抛出错误来报告任何问题。OpFamilyMember
的依赖相关字段由核心代码初始化,以创建对操作符类的硬依赖(如果这是 CREATE OPERATOR CLASS
),或对 operator family 的软依赖(如果这是 ALTER OPERATOR FAMILY ADD
)。如果其他一些行为更合适,amadjustmembers
可以调整这些字段。例如,GIN、GiST 和 SP-GiST 总会将操作符成员设置为对操作符家族具有软依赖,因为在这些索引类型中操作符与操作符类之间的连接相对较弱;所以允许自由地添加和移除操作符成员是合理的。可选的支持函数通常也会获得软依赖,以便在必要时可以移除它们。
当然,索引的目的是支持对匹配可索引 WHERE
条件(通常称为限定符或扫描键)的元组的扫描。在下面的 第 62.3 节 中更全面地描述了索引扫描的语义。索引访问方法可以支持“普通”索引扫描、“位图”索引扫描或两者兼有。索引访问方法必须或可以提供的扫描相关函数有
IndexScanDesc ambeginscan (Relation indexRelation, int nkeys, int norderbys);
准备扫描索引。nkeys
和 norderbys
参数表明在扫描中将使用的限定符和排序运算符的数量;这些信息对于空间分配目的可能很有用。请注意,尚未提供扫描键的实际值。结果必须是一个已分配的结构。由于实现原因,索引访问方法必须通过调用 RelationGetIndexScan()
创建此结构。在大多数情况下,ambeginscan
除了进行该调用之外几乎没有其他作用,也许还要获取锁;索引扫描启动的有趣部分在 amrescan
中。
void amrescan (IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int norderbys);
启动或重新启动索引扫描,有可能会使用新的扫描键。(要使用之前传递的键重新启动,则对 keys
和/或 orderbys
传递 NULL。)请注意,键或排序运算符的数量不得大于传递给 ambeginscan
的数量。在实践中,当嵌套循环连接选择了一个新的外部元组,并且需要一个新键比较值时,会使用重新启动功能,但扫描键结构保持不变。
bool amgettuple (IndexScanDesc scan, ScanDirection direction);
在给定扫描中获取下一个元组,以给定的方向(在索引中向前或向后)移动。如果获取到元组,则返回 true,如果没有剩余匹配元组,则返回 false。在 true 情况下,元组 TID 存储到 scan
结构。请注意,“成功” 仅意味着索引包含与扫描键匹配的条目,并不意味着元组必定仍然存在于堆中或将通过调用方的快照测试。如果成功,amgettuple
也必须将 scan->xs_recheck
设置为 true 或 false。False 表示可以确定索引项与扫描键匹配。True 表示不确定,且扫描键表示的条件必须在获取堆元组后对此条件进行重新检查。此规定支持“有损”索引运算符。请注意,重新检查将仅限于扫描条件;amgettuple
调用方永远不会重新检查部分索引谓词(如果有)。
如果索引支持index-only scans(即,amcanreturn
为其任何列返回 true),那么在成功的情况下 AM 还必须检查 scan->xs_want_itup
,如果为 true 则必须为索引项返回最初索引的数据。amcanreturn
返回 false 的列可以作为 null 返回。数据可以以存储在 scan->xs_itup
中的 IndexTuple
指针形式返回,元组描述符为 scan->xs_itupdesc
;或以存储在 scan->xs_hitup
中的 HeapTuple
指针形式返回,元组描述符为 scan->xs_hitupdesc
。(在重建可能不适合放入 IndexTuple
的数据时应使用后者格式。)在任何情况下,由访问方法负责管理由指针引用的数据。该数据必须至少保留到对该扫描执行下一个 amgettuple
、amrescan
或 amendscan
调用之前。
仅当访问方法支持“plain”索引扫描时,才需要提供 amgettuple
函数。如果不支持,则其 IndexAmRoutine
结构中的 amgettuple
字段必须设置为 NULL。
int64 amgetbitmap (IndexScanDesc scan, TIDBitmap *tbm);
获取给定扫描中的所有元组,并将它们添加到调用方提供的 TIDBitmap
中(即,将元组 ID 集 OR 到已在位图中的任何集合)。返回已获取的元组数(这可能只是一个近似计数,例如,某些 AM 不会检测重复项)。在向位图中插入元组 ID 时,amgetbitmap
可以指示需要对特定元组 ID 重新检查扫描条件。这与 amgettuple
的 xs_recheck
输出参数类似。注意:在当前实现中,对该功能支持与对位图本身有损存储的支持相结合,因此调用方重新检查重新检查元组的扫描条件和部分索引谓词(如果有)。然而,这可能并不总是成立。amgetbitmap
和 amgettuple
不能在同一个索引扫描中使用;在使用 amgetbitmap
时还有其他限制,如 第 62.3 节 中所述。
仅当访问方法支持“bitmap”索引扫描时,才需要提供 amgetbitmap
函数。如果不支持,则其 IndexAmRoutine
结构中的 amgetbitmap
字段必须设置为 NULL。
void amendscan (IndexScanDesc scan);
结束扫描并释放资源。scan
结构本身不应该被释放,但访问方法内部获取的任何锁或引脚都必须被释放,以及 ambeginscan
和其他与扫描相关的函数分配的任何其他内存。
void ammarkpos (IndexScanDesc scan);
标记当前扫描位置。访问方法只需要支持每个扫描的一个已记住的扫描位置。
ammarkpos
函数只需要在访问方法支持有序扫描时提供。如果不支持,IndexAmRoutine
结构中的 ammarkpos
字段可以设置为 NULL。
void amrestrpos (IndexScanDesc scan);
将扫描恢复到最近标记的位置。
amrestrpos
函数只需要在访问方法支持有序扫描时提供。如果不支持,IndexAmRoutine
结构中的 amrestrpos
字段可以设置为 NULL。
除了支持普通索引扫描,某些类型的索引可能希望支持并行索引扫描,它允许多个后端合作执行索引扫描。索引访问方法应该安排各项事务,以便每个合作进程返回普通未并行索引扫描将执行但以这种方式执行元组的子集,由此子集联合等于普通未并行索引扫描将返回的元组集。此外,虽然并行扫描返回的元组无需有任何全局顺序,但每个合作后端内返回的元组子集的顺序必须与请求的顺序相符。可以实现以下函数以支持并行索引扫描:
Size amestimateparallelscan (int nkeys, int norderbys);
估计并返回执行并行扫描访问方法所需的动态共享内存的字节数。(此数字是除 ParallelIndexScanDescData
中 AM 无关数据的所需空间外,而非代替。)
nkeys
和 norderbys
参数指示扫描中将使用比较运算符和排序运算符的数量;相同的数值将传递给 amrescan
。请注意,尚未提供扫描键的实际值。
对于不支持并行扫描或所需其他存储字节数为零的访问方法,有必要实现该函数。
void aminitparallelscan (void *target);
此函数将在并行扫描开始时被调用以初始化动态共享内存。 target
将至少指向先前由 amestimateparallelscan
返回的字节数,且此函数可以使用该空间量来存储所需的任何数据。
对于不支持并行扫描或者所需的共享内存空间不需要初始化的访问方法,不必实现此函数。
void amparallelrescan (IndexScanDesc scan);
如果已实现此函数,则在必须重新启动并行索引扫描时将调用此函数。它应重置由 aminitparallelscan
设置的所有共享状态,以便从开头重新开始扫描。