索引访问方法必须在IndexAmRoutine
中提供的索引构造和维护函数有:
IndexBuildResult * ambuild (Relation heapRelation, Relation indexRelation, IndexInfo *indexInfo);
创建一个新索引。索引关系已经被物理创建,但是是空的。必须用索引访问方法要求的固定数据填充它,外加所有已经在表里的行的项。通常,ambuild
函数会调用table_index_build_scan()
来扫描表以获取现有元组并计算需要被插入到索引的键。该函数必须返回一个已分配内存的结构,其中包含关于新索引的统计信息。
void ambuildempty (Relation indexRelation);
构建一个空索引,并且把它写入到给定关系的初始化分叉中(
INIT_FORKNUM
)。只会为不做日志的索引调用这个方法,被写入到
初始化分叉的空索引在每次服务器启动时将被复制到主关系分叉中。
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
的值。
该函数的布尔结果值仅仅在checkUnique
为UNIQUE_CHECK_PARTIAL
时才有意义。这种情况下一个“真”结果意味着这个新项是已知唯一的,反之“假”结果意味着它可能不 是唯一的(并且一个延迟的唯一性校验必须是预定的)。对于其他情况,建议使用一个常量“假”结果。
有些索引可能不会索引所有元组。如果元组不被索引,aminsert
应该仅返回而什么都不做。
如果索引AM希望在SQL语句中连续的索引插入之间缓冲数据,它可以在indexInfo->ii_Context
中分配空间并且在indexInfo->ii_AmCache
(初始为NULL)中存放一个指向该数据的指针。
IndexBulkDeleteResult * ambulkdelete (IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state);
从索引中删除元组。这是一个“批量删除”操作,它的意图是通过扫描整个索引并检查每个项看它是否需要被删除。被传递进来的callback
函数必须被调用(调用风格是:callback(
)来判断任何其引用的 TID 标识的索引项是否需要删除。必须返回 NULL 或者是一个 palloc 过的、 包含删除操作效果的统计信息的结构。如果不需要向TID
, callback_state) returns boolamvacuumcleanup
传递信息,返回 NULL 也是 OK 的。
由于maintenance_work_mem
被限制,在删除多行的时候ambulkdelete
可能需要被调用多次。stats
参数是对这个索引上一次调用的结果(在一个VACUUM
操作中第一次调用时是 NULL)。这将允许 AM 在整个操作过程中积累统计信息。典型的,如果被传递的stats
非空,ambulkdelete
将会修改并返回相同的结构。
IndexBulkDeleteResult * amvacuumcleanup (IndexVacuumInfo *info, IndexBulkDeleteResult *stats);
在一个VACUUM
操作(零个或更多次ambulkdelete
调用)后清空。虽然不必做任何返回索引统计信息之外的事情,但是它可能执行批量清理,例如回收空索引页面。stats
是最后一次ambulkdelete
调用返回的东西或者 NULL(如果没有元组需要删除而未调用ambulkdelete
)。如果结果不是 NULL,那么它必须是一个已经被 palloc 的结构。它包含的统计信息将用于更新pg_class
并且由VACUUM
报 告(如果给出了VERBOSE
)。如果索引在VACUUM
操作期间根本没有改变,那么返回 NULL 也是可以的,否则必须返回正确的统计信息。
amvacuumcleanup
将也会在一个ANALYZE
操作结束时被调用。这种情况中stats
总是 NULL 并且任何返回值都将会被忽略。这种情况可以通过检测info->analyze_only
来区分。我们建议,在这样的调用中访问方法除了做插入后的清理之外什么也不做,并且那是仅仅是在一个自动清理工作者进程中。
bool amcanreturn (Relation indexRelation, int attno);
通过返回型为一个IndexTuple
的索引项的被索引列值,检查索引是否能在给定列上支持只用索引的扫描。属性编号从 1 开始编号,即第一列的 attno 是 1。如果支持返回 TRUE,否则返回 FALSE。
对于包含列(如果支持),此函数将始终返回 true,因为包含列不能被检索是没有意义的。
如果访问方法 完全不支持只用索引的扫描,其IndexAmRoutine
结构中的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
为真时,如果任何一个选项都不可识别或者含有非法值,该函数都应当报告一个适当的错误消息;当validate
为假时,非法 项应该被安静地忽略(当正在载入的选项已经在pg_catalog
中时, validate
为假;仅在访问方法已经改变了选项的规则时才可能找 到非法项,并且在此情况下忽略废弃的项是合适的)。如果想要默认行为,那么返回 NULL 也 OK。
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。否则,对于pg_indexam_has_property
调用会使用均为 0 的index_oid
和attno
参数来调用amproperty
方法;对于pg_index_has_property
调用会使用有效的index_oid
和为 0 的attno
参数来调用amproperty
方法;对于pg_index_column_has_property
调用会使用有效的index_oid
以及大于零的attno
参数来调用amproperty
方法。prop
是用于标识被测试性质的枚举值,而propname
是原始的性质名称字符串。如果核心代码不能识别该性质名称,则prop
为AMPROP_UNKNOWN
。访问方法可以通过检查propname
是否匹配(为与核心代码一致,使用pg_strcasecmp
来匹配)来定义自定义性质名称;对于核心代码已知的名称,最好检查prop
。
如果amproperty
方法返回true
则表示它已经确定了性质测试的结果:它必定会设置*res
为要返回的布尔值,如果要返回 NULL 则设置*isnull
为true
(两个被引用的变量在调用之前要被初始化为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);
只要访问方法能够,为指定的操作符类验证系统目录项。例如,这可能包括所有所需支持函数所提供的测试。如果该 opclass 不合法,amvalidate
函数必须返回假。所存在的问题应由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
在 opclass 的情况下,
核心代码使用对结构的依赖字段的CREATE OPERATOR CLASS
硬依赖进行初始化。
ALTER OPERATOR FAMILY ADD
如果是这样,用软依赖初始化 opfamily。
如果其他行为更合适, amadjustmembers
可以调整这些字段。
比如GIN、GiST、SP-GiST等索引格式,算子和opclass的相关性比较低,可以自由添加或删除算子成员,
总是对opfamily设置软依赖。对其他支持功能设置软依赖项也很常见,以便在必要时可以将其删除。
当然,索引的目的是支持扫描那些匹配一个可索引WHERE
情况的元组,常常也被称为限定词或扫描键。索引扫描的语义在下面的第 62.3 节中有完整的描述。一个索引访问方法可以支持“普通”索引扫描、“位图”索引扫描或者两者。一个索引访问方法必须或可能提供的与扫描相关的函数是:
IndexScanDesc ambeginscan (Relation indexRelation, int nkeys, int norderbys);
为一个索引扫描做准备。nkeys
和norderbys
参数说明要被用在扫描中的条件和排序操作符的数目,它们可以用于空间分配目的。注意扫描键的实际值还没有被提供。结果必须是一个 palloc 过的结构。由于实现的原因,索引访问方法必须通过调用RelationGetIndexScan()
来创建这个结构。在大多数情况中,ambeginscan
除了做这个调用和获取锁之外不会做很多工作,索引扫描启动中有趣的部分在amrescan
中。
void amrescan (IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int norderbys);
开始或者重新开始一个索引扫描,可能使用的是一个新的扫描键(要想使用之前传递的键重新开始,给keys
和/或orderbys
传递 NULL)。请注意,使用的键或排序操作符的个数不能大于传递给ambeginscan
的个数。实际上这个重新开始特性的使用场景是:在一个嵌套循环连接选取了一个新的 outer 元组时,因此需要一个新的键比较值,但扫描键结构仍然保持相同。
bool amgettuple (IndexScanDesc scan, ScanDirection direction);
在给定扫描中取下一个元组,向给定方向移动(在索引中向前或者向后)。如果取到了元组,则返回 TRUE,如果取到匹配的元组,返回 FALSE。在 TRUE 的情况中,该元组的 TID 被存储在scan
结构中。请注意“成功”只 意味着索引包含一个匹配扫描键的项,并不意味该元组仍然在堆中存在, 或者是能够通过调用者的快照测试。在成功时,amgettuple
也必须把scan->xs_recheck
设 置成 TRUE 或者 FALSE。FALSE 意味着它确定索引项匹配扫描键。TRUE 意味着它并不确定,而且必须在取得堆元组之后对它重新检查扫描键表示的条件。 这条规定支持“有损的”索引操作符。注意重新检查仅仅对扫描条件扩展;一个部分索引谓语(如果有)从不被amgettuple
调用者重新检查。
如果索引支持只用索引扫描(即amcanreturn
对其任何一列返回 TRUE),则在成功时 AM 也必须检查scan->xs_want_itup
,并且如果检查为真它必须返回索引项的原始被索引数据。amcanreturn
返回false的列可以作为nulls返回。该数据的返回形式可以是一个存储在scan->xs_itup
中的IndexTuple
指针外加元组描述符scan->xs_itupdesc
,或者是一个存储在scan->xs_hitup
中的HeapTuple
指针外加元组描述符scan->xs_hitupdesc
(在重构可能无法放在一个IndexTuple
中的数据时,应该使用后一种格式)。不管是哪种形式,访问方法应该负责管理好指针引用的数据。至少在为扫描下一次调用amgettuple
、amrescan
或amendscan
之前,该数据必须是完好的。
如果访问方法支持“普通”索引扫描,只需要提供amgettuple
函数。如果不支持,它的IndexAmRoutine
结构的amgettuple
域必须被设置为 NULL。
int64 amgetbitmap (IndexScanDesc scan, TIDBitmap *tbm);
在给定扫描中取所有元组并且把它们添加到调用者提供的TIDBitmap
中(即,把元组 ID 的集合 OR 到已经存在于位图中的东西里面)。返回被取得的元组的数量(这可能仅仅是一个近似计数,例如一些 AM 不会去重)。在把元组 ID 插入到位图时,amgetbitmap
可以指明对指定元组 ID 要求重新检查扫描条件。这与amgettuple
的 xs_recheck
输出参数类似。注意:在当前的实现中,这个特性的支持是和对位图本身有损存储的支持合并在一起的,并且调用者会对可重新检查的元组检查扫描条件和部分索引谓词(如果有)。但是,那不会总是真的。amgetbitmap
和amgettuple
不能被用于同一个索引扫描;正如第 62.3 节中所解释的,在使用amgetbitmap
时也有其他的限制条件。
如果访问方法支持“bitmap”索引扫描,则仅需要提供amgetbitmap
函数。如果不支持,它的IndexAmRoutine
结构中的amgetbitmap
域必须被设置为 NULL。
void amendscan (IndexScanDesc scan);
结束扫描并释放资源。不应该释放scan
结构本身,但访问方法内部使用的任何锁或者 pin 都应该被释放,
以及ambeginscan
和其他扫描相关函数分配的任何其他内存。
void ammarkpos (IndexScanDesc scan);
标记当前扫描位置。访问方法只需要支持每个扫描里面有一个被标记的扫描位置。
ammarkpos
函数只有在访问方法支持有序扫描时才需要提供。如果不支持,则访问方法的IndexAmRoutine
结构的ammarkpos
域可以设置为 NULL。
void amrestrpos (IndexScanDesc scan);
把扫描恢复到最近标记的位置。
amrestrpos
函数只有在访问方法支持有序扫描时才需要提供。如果不支持,则访问方法的IndexAmRoutine
结构的amrestrpos
域可以设置为 NULL。
除了支持普通的索引扫描之外,某些类型的索引可能希望支持并行索引扫描,这种方式允许多个后端合作来执行一次索引扫描。索引访问方法应该安排好各种事情,这样每个参与合作的进程才能返回原本会由普通非并行索引扫描执行得到的元组的一个子集,但是得到的那些子集的并集应该等于普通非并行索引扫描得到的元组集合。此外,虽然不需要并行扫描返回的元组有任何全局顺序,但每个参与合作的后端中返回的元组子集的顺序必须匹配所要求的顺序。必须实现下列函数才能支持并行索引扫描:
Size amestimateparallelscan (void);
估算并且返回访问方法执行一次并行扫描所需要的动态共享内存的字节数(这个数字是对ParallelIndexScanDescData
中访问方法无关的数据所需空间量的补充而不是替代)。
对于不支持并行扫描或者额外存储需求的的字节数为零的访问方法,无需实现这个函数。
void aminitparallelscan (void *target);
在一次并行扫描的开头将调用这个函数来初始化动态共享内存。target
将指向一段动态共享内存空间,其大小至少为之前amestimateparallelscan
返回的字节数,并且这个函数可以使用这部分空间来存放它希望存放的任何数据。
对于不支持并行扫描或者不要求初始化共享内存空间的情况,无需实现这个函数。
void amparallelrescan (IndexScanDesc scan);
如果实现了这个函数,当并行索引扫描必须被重启时,将会调用这个函数。它应该重置由aminitparallelscan
建立的任何共享状态,这样扫描将会被重头重新开始。