#include "postgres.h" #include #include "fmgr.h" #include "access/xlog.h" #include "replication/walreceiver.h" #include "utils/elog.h" #include "utils/builtins.h" #include "utils/timestamp.h" #include "funcapi.h" #include "access/htup_details.h" #include "catalog/pg_type.h" #include "utils/pg_lsn.h" #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif PG_FUNCTION_INFO_V1(get_rcv_replication_stat); Datum get_rcv_replication_stat(PG_FUNCTION_ARGS) { Assert(PG_NARGS() == 0); // 表示没有输入参数 if (!RecoveryInProgress()) // 在数据库处于恢复状态下时运行,否则不允许 ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("recovery is not in progress"), errhint("This functions can only be executed during recovery."))); /* use volatile pointer to prevent code rearrangement */ volatile WalRcvData *walrcv = WalRcv; // 共享内存中用于管理流复制的数据结构 src/include/replication/walreceiver.h TupleDesc tupdesc; // 创建一个行描述变量 Datum DatumValue[8]; // 创建一个存储值的Datum数组, 需要返回几个字段, 创建相应长度的数组 bool nulls[8]; // 元组中的属性数 typedef struct TupleDescData -> natts; /* Initialise DatumValue and NULL flags arrays 初始化 */ MemSet(DatumValue, 0, sizeof(DatumValue)); MemSet(nulls, 0, sizeof(nulls)); /* Initialise attributes information in the tuple descriptor 定义字段类型和字段名, 到相应的头文件src/include/catalog/pg_type.h找到对应的类型 */ /* CreateTemplateTupleDesc 该函数分配一个空的元组描述符结构。 元组类型的ID信息被初始设置为一个匿名记录类型; 如果需要调用者可以覆盖此。 TupleDescInitEntry: 此函数在先前分配的元组描述符中初始化单个属性结构。 如果attributeName为NULL,则attname字段将设置为空字符串(这是在我们不知道或不需要该字段名称的情况下)。 另外,某些调用者使用此功能来更改现有tupdesc中与数据类型相关的字段;例如, 它们传递attributeName = NameStr(att-> attname)来指示不应修改attname字段。 请注意,attcollation设置为指定数据类型的默认值。 如果需要非默认排序规则,请稍后使用TupleDescInitEntryCollation插入它。 */ tupdesc = CreateTemplateTupleDesc(8, false); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "last_walend_time", TIMESTAMPTZOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "last_recv_lsn", LSNOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 3, "last_apply_lsn", LSNOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 4, "last_apply_delay_ms", INT4OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 5, "receiver_pid", INT4OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 6, "receiver_state", INT4OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 7, "receiver_start_time", INT8OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 8, "receiver_conninfo", TEXTOID, -1, 0); BlessTupleDesc(tupdesc); // 完成对返回类型的构造, 参考src/include/funcapi.h // 接下来将每个值转换为对应的Datum存储到DatumValue数组, 对应的nulls数组仅当值为空时设置为true. TimestampTz receipttime; receipttime = walrcv->latestWalEndTime; // TimestampTz latestWalEndTime; DatumValue[0] = TimestampTzGetDatum(receipttime); // #define TimestampTzGetDatum(X) Int64GetDatum(X) XLogRecPtr recvPtr; // src/include/access/xlogdefs.h 指向XLOG中的位置的指针。 这些指针为64位宽,因为我们不希望它们溢出。 /* src/backend/replication/walreceiverfuncs.c: GetWalRcvWriteRecPtr 返回walreceiver已写入的最后1个字节位置。 (可选)返回上一个块开始,即在最近的walreceiver刷新周期中写入的第一个字节。 对该值不感兴趣的调用者可以为lastChunkStart传递NULL。 与receiveTLI相同。 */ recvPtr = GetWalRcvWriteRecPtr(NULL, NULL); // XLogRecPtr recptr; if (recvPtr == 0) nulls[1] = true; else DatumValue[1] = LSNGetDatum(recvPtr); // #define LSNGetDatum(X) (Int64GetDatum((int64) (X))) /* xlog.c: GetXLogReplayRecPtr 获取最新的重做apply position。 导出以允许WALReceiver直接读取指针。 */ XLogRecPtr applyPtr; applyPtr = GetXLogReplayRecPtr(NULL); if (recvPtr == 0) nulls[2] = true; else DatumValue[2] = LSNGetDatum(applyPtr); int apply_delay_ms; /* walreceiverfuncs.c: GetReplicationApplyDelay 如果没有应用延迟信息,则返回复制应用延迟(以毫秒为单位)或-1 */ apply_delay_ms = GetReplicationApplyDelay(); if (apply_delay_ms == -1) nulls[3] = true; else DatumValue[3] = Int32GetDatum(apply_delay_ms); DatumValue[4] = Int32GetDatum(walrcv->pid); DatumValue[5] = Int32GetDatum(walrcv->walRcvState); DatumValue[6] = Int64GetDatum(walrcv->startTime); DatumValue[7] = PointerGetDatum(cstring_to_text((char *)walrcv->conninfo)); // 返回 /* Returns the record as Datum 把元组变成 datum */ PG_RETURN_DATUM(HeapTupleGetDatum( heap_form_tuple(tupdesc, DatumValue, nulls))); // heap_form_tuple 构造一个tuple 返回 HeapTupleHeader , HeapTupleGetDatum 将HeapTupleHeader指针转换为Datum。 }