forked from huawei/openGauss-server
620 lines
28 KiB
C++
620 lines
28 KiB
C++
/* ---------------------------------------------------------------------------------------
|
||
* *
|
||
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
||
*
|
||
* openGauss is licensed under Mulan PSL v2.
|
||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||
* You may obtain a copy of Mulan PSL v2 at:
|
||
*
|
||
* http://license.coscl.org.cn/MulanPSL2
|
||
*
|
||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||
* See the Mulan PSL v2 for more details.
|
||
* ---------------------------------------------------------------------------------------
|
||
*
|
||
* gs_xlogdump.cpp
|
||
*
|
||
* IDENTIFICATION
|
||
* src/gausskernel/storage/page/gs_xlogdump.cpp
|
||
*
|
||
* ---------------------------------------------------------------------------------------
|
||
*/
|
||
|
||
#include <stdio.h>
|
||
#include "c.h"
|
||
#include "access/tupdesc.h"
|
||
#include "access/xact.h"
|
||
#include "access/xlog_basic.h"
|
||
#include "access/xlogdefs.h"
|
||
#include "access/xlogreader.h"
|
||
#include "access/xlog_internal.h"
|
||
#include "catalog/catalog.h"
|
||
#include "lib/stringinfo.h"
|
||
#include "nodes/pg_list.h"
|
||
#include "postgres.h"
|
||
#include "storage/smgr/relfilenode.h"
|
||
#include "utils/builtins.h"
|
||
#include "utils/fmgroids.h"
|
||
#include "utils/palloc.h"
|
||
#include "pageparse.h"
|
||
|
||
typedef struct XLogPrivate {
|
||
const char *datadir;
|
||
TimeLineID tli;
|
||
} XLogPrivate;
|
||
|
||
typedef struct XLogFilter {
|
||
TransactionId by_xid;
|
||
bool by_xid_enabled;
|
||
bool by_tablepath_enabled;
|
||
bool by_block;
|
||
RelFileNode by_relfilenode;
|
||
BlockNumber blocknum;
|
||
} XLogFilter;
|
||
|
||
//通过组合数据目录路径、起始LSN和结束LSN,生成一个格式化的输出文件名
|
||
static void GenerateOutputFileName(char *outputFilename, char *start_lsn_str, char *end_lsn_str)
|
||
{
|
||
List *elemlist = NIL;
|
||
//将起始LSN字符串按照'/'进行分割,将元素存储在elemlist中
|
||
SplitIdentifierString(start_lsn_str, '/', &elemlist);
|
||
//获取elemlist中第二个元素,赋值给start_lsn_str_p2变量
|
||
char *start_lsn_str_p2 = (char *)lsecond(elemlist);
|
||
//释放elemlist占用的内存
|
||
list_free_ext(elemlist);
|
||
//将结束LSN字符串按照'/'进行分割,将元素存储在elemlist中
|
||
SplitIdentifierString(end_lsn_str, '/', &elemlist);
|
||
//获取elemlist中第二个元素,赋值给end_lsn_str_p2变量
|
||
char *end_lsn_str_p2 = (char *)lsecond(elemlist);
|
||
//格式化输出文件名,并将结果写入outputFilename的末尾
|
||
int rc = snprintf_s(outputFilename + (int)strlen(outputFilename), MAXFILENAME, MAXFILENAME - 1, "%s/%s_%s.xlog",
|
||
t_thrd.proc_cxt.DataDir, start_lsn_str_p2, end_lsn_str_p2);
|
||
//检查格式化操作是否成功执行
|
||
securec_check_ss(rc, "\0", "\0");
|
||
}
|
||
//验证LSN字符串,并将其转换为XLogRecPtr类型的LSN指针
|
||
static void ValidateLSN(char *lsn_str, XLogRecPtr *lsn_ptr)
|
||
{
|
||
uint32 hi = 0;
|
||
uint32 lo = 0;
|
||
//调用validate_xlog_location函数验证LSN字符串的有效性
|
||
validate_xlog_location(lsn_str);
|
||
//使用sscanf_s函数从LSN字符串中解析出hi和lo两个部分的值
|
||
if (sscanf_s(lsn_str, "%X/%X", &hi, &lo) != TWO)
|
||
//如果解析失败,则抛出错误并提示无法解析LSN字符串
|
||
ereport(ERROR,
|
||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not parse xlog location \"%s\"", lsn_str)));
|
||
//将hi和lo组合成64位的LSN值,存储到lsn_ptr中
|
||
*lsn_ptr = (((uint64)hi) << XIDTHIRTYTWO) | lo;
|
||
}
|
||
//验证起始LSN和结束LSN,并将它们转换为XLogRecPtr类型的LSN指针
|
||
static void ValidateStartEndLSN(char *start_lsn_str, char *end_lsn_str, XLogRecPtr *start_lsn, XLogRecPtr *end_lsn)
|
||
{
|
||
//验证起始LSN字符串并将其转换为XLogRecPtr类型的起始LSN指针
|
||
ValidateLSN(start_lsn_str, start_lsn);
|
||
//验证结束LSN字符串并将其转换为XLogRecPtr类型的结束LSN指针
|
||
ValidateLSN(end_lsn_str, end_lsn);
|
||
//检查结束LSN是否小于起始LSN,如果是则抛出错误
|
||
if (XLByteLT(*end_lsn, *start_lsn))
|
||
/*
|
||
* 如果结束LSN小于起始LSN,则抛出错误。
|
||
* 错误信息包含起始LSN和结束LSN的具体数值。
|
||
* 使用% X格式将高位32位和低位32位转换为十六进制数值。
|
||
*/
|
||
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||
errmsg("start xlog location %X/%X should be smaller than or equal to end xlog location %X/%X",
|
||
(uint32)(*start_lsn >> XIDTHIRTYTWO), (uint32)(*start_lsn), (uint32)(*end_lsn >> XIDTHIRTYTWO),
|
||
(uint32)(*end_lsn))));
|
||
}
|
||
//获取当前数据库日志的最小LSN,用于作为起始LSN的参考值
|
||
static XLogRecPtr GetMinLSN()
|
||
{
|
||
//获取最后一个已移除的日志段号
|
||
XLogSegNo lastRemovedSegNo = XLogGetLastRemovedSegno();
|
||
//计算当前LSN
|
||
XLogRecPtr current_recptr = (lastRemovedSegNo + 1) * XLogSegSize;
|
||
//返回当前LSN
|
||
return current_recptr;
|
||
}
|
||
//获取当前数据库日志的最大LSN,用于作为结束LSN的参考值
|
||
static XLogRecPtr GetMaxLSN()
|
||
{
|
||
//如果正在进行恢复,则抛出错误并提示无法获取最大LSN
|
||
if (RecoveryInProgress())
|
||
ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("recovery is in progress"),
|
||
errhint("Can't get local max LSN during recovery.")));
|
||
//获取当前日志记录的写入指针位置
|
||
XLogRecPtr current_recptr = GetXLogWriteRecPtr();
|
||
//返回当前日志记录的写入指针位置,即最大LSN
|
||
return current_recptr;
|
||
}
|
||
//将XLog记录信息输出到字符串
|
||
static void XLogDumpDisplayRecord(XLogReaderState *record, char *strOutput)
|
||
{
|
||
errno_t rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1,
|
||
"start_lsn: %X/%X \nend_lsn: %X/%X \nxid: " XID_FMT " \nterm: %u \ntotal length: %u \ndesc: %s - ",
|
||
(uint32)(record->ReadRecPtr >> XIDTHIRTYTWO), (uint32)record->ReadRecPtr,
|
||
(uint32)(record->EndRecPtr >> XIDTHIRTYTWO), (uint32)record->EndRecPtr, XLogRecGetXid(record),
|
||
XLogRecGetTerm(record), XLogRecGetTotalLen(record), RmgrTable[XLogRecGetRmid(record)].rm_name);
|
||
securec_check_ss(rc, "\0", "\0");
|
||
StringInfoData buf;
|
||
initStringInfo(&buf);
|
||
RmgrTable[XLogRecGetRmid(record)].rm_desc(&buf, record);
|
||
//输出块引用的信息
|
||
rc = strcat_s(strOutput, MAXOUTPUTLEN, buf.data);
|
||
securec_check(rc, "\0", "\0");
|
||
//如果是bucket文件节点,输出bucketNode信息
|
||
if (!XLogRecHasAnyBlockRefs(record)) {
|
||
rc = strcat_s(strOutput, MAXOUTPUTLEN, "\n\n");
|
||
securec_check(rc, "\0", "\0");
|
||
return;
|
||
}
|
||
|
||
if (record->max_block_id >= 0) {
|
||
rc = strcat_s(strOutput, MAXOUTPUTLEN, "\nothers: ");
|
||
securec_check(rc, "\0", "\0");
|
||
}
|
||
|
||
for (int block_id = 0; block_id <= record->max_block_id; block_id++) {
|
||
if (!XLogRecHasBlockRef(record, block_id))
|
||
continue;
|
||
/* Print format: others: rel %u/%u/%u/%d fork %s blk %u */
|
||
RelFileNode rnode;
|
||
ForkNumber forknum;
|
||
BlockNumber blk;
|
||
if (!XLogRecGetBlockTag(record, block_id, &rnode, &forknum, &blk))
|
||
continue;
|
||
rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, "\n\trel %u/%u/%u",
|
||
rnode.spcNode, rnode.dbNode, rnode.relNode);
|
||
securec_check_ss(rc, "\0", "\0");
|
||
if (IsBucketFileNode(rnode)) { /* check between InvalidBktId and SegmentBktId */
|
||
rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1,
|
||
"/%d", rnode.bucketNode);
|
||
securec_check_ss(rc, "\0", "\0");
|
||
}
|
||
rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, ", fork %s",
|
||
forkNames[forknum]);
|
||
securec_check_ss(rc, "\0", "\0");
|
||
rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, ", blk %u", blk);
|
||
securec_check_ss(rc, "\0", "\0");
|
||
/* others: lastlsn %X/%X" */
|
||
// 输出lastlsn信息
|
||
XLogRecPtr lsn;
|
||
XLogRecGetBlockLastLsn(record, block_id, &lsn);
|
||
rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, ", lastlsn %X/%X",
|
||
(uint32)(lsn >> XIDTHIRTYTWO), (uint32)lsn);
|
||
securec_check_ss(rc, "\0", "\0");
|
||
}
|
||
rc = strcat_s(strOutput, MAXOUTPUTLEN, "\n\n");
|
||
securec_check(rc, "\0", "\0");
|
||
}
|
||
|
||
void CheckOpenFile(FILE *outputfile, char *outputFilename)
|
||
{
|
||
// 检查文件打开操作的结果
|
||
if (outputfile == NULL)
|
||
ereport(ERROR, (errcode(ERRCODE_FILE_READ_FAILED), (errmsg("Cannot read %s", outputFilename))));
|
||
}
|
||
|
||
void CheckWriteFile(int result, int cnt_len, char *outputFilename)
|
||
{
|
||
// 检查文件写入操作的结果
|
||
if (result != cnt_len)
|
||
ereport(ERROR, (errcode(ERRCODE_FILE_WRITE_FAILED), (errmsg("Cannot write %s", outputFilename))));
|
||
}
|
||
|
||
void CheckCloseFile(int result, char *outputFilename)
|
||
{
|
||
//检查文件关闭操作的结果
|
||
if (0 != result)
|
||
ereport(ERROR, (errcode(ERRCODE_IO_ERROR), (errmsg("Cannot close %s", outputFilename))));
|
||
}
|
||
//检查当前XLog记录是否符合一组给定的筛选条件
|
||
static bool CheckValidRecord(XLogReaderState *xlogreader_state, XLogFilter *filter)
|
||
{
|
||
bool found = false;//初始化 found 变量,表示是否找到满足条件的块
|
||
//循环遍历 XLog 中的每个块
|
||
for (int i = 0; i <= xlogreader_state->max_block_id; i++) {
|
||
RelFileNode rnode;
|
||
ForkNumber forknum;
|
||
BlockNumber blk;
|
||
//获取当前块的标记
|
||
if (!XLogRecGetBlockTag(xlogreader_state, i, &rnode, &forknum, &blk))
|
||
continue;//如果返回 false,则说明没有找到块标记,跳过该块继续循环
|
||
//检查块标记是否符合查询条件
|
||
if (RelFileNodeEquals(rnode, filter->by_relfilenode))
|
||
/* if equal to specific block or check all blocks, found = ture; */
|
||
found = (!filter->by_block) || (blk == filter->blocknum);
|
||
//如果找到一个符合条件的块,则返回true表示当前记录有效
|
||
if (found) {
|
||
return found;
|
||
}
|
||
}
|
||
//没有找到任何符合条件的块,则返回false表示当前记录无效
|
||
return found;
|
||
}
|
||
//在一系列连续的日志记录中查找下一个有效的日志记录的LSN
|
||
static XLogRecPtr UpdateNextLSN(XLogRecPtr cur_lsn, XLogRecPtr end_lsn, XLogReaderState *xlogreader_state, bool *found)
|
||
{
|
||
XLogRecPtr next_record = InvalidXLogRecPtr;// 初始化 next_record,表示下一个记录的LSN
|
||
// 使用循环尝试多次查找下一个记录的LSN
|
||
for (int tryTimes = 0; tryTimes < FIVE; tryTimes++) {
|
||
// 计算起始 LSN
|
||
XLogRecPtr start_lsn = Max(cur_lsn, (g_instance.comm_cxt.predo_cxt.redoPf.oldest_segment) * XLogSegSize);
|
||
// 查找下一个记录的LSN
|
||
next_record = XLogFindNextRecord(xlogreader_state, start_lsn);
|
||
// 检查找到的下一个记录的LSN是否有效并且小于结束LSN
|
||
if (!XLByteEQ(next_record, InvalidXLogRecPtr) && XLByteLT(next_record, end_lsn)) {
|
||
*found = true;// 设置 found 为 true,表示找到了下一个记录的LSN
|
||
return next_record;// 返回找到的下一个记录的LSN
|
||
}
|
||
}
|
||
// 如果没有找到下一个记录的LSN,则返回 InvalidXLogRecPtr
|
||
return next_record;
|
||
}
|
||
|
||
/*
|
||
start_lsn: 起始位置的LSN(Log Sequence Number)
|
||
end_lsn: 结束位置的LSN
|
||
filter: 筛选器,用于选择要输出的记录
|
||
outputFilename: 目标输出文件的名称
|
||
readprivate: 一个XLogPrivate类型的结构体,用于初始化XLogReaderState
|
||
xlogreader_state: XLogReaderState类型的对象,用于读取WAL文件
|
||
first_record: 第一个有效记录的位置,即起始位置之后的第一个有效记录位置
|
||
valid_start_lsn: 真正处理的起始位置,由于可能找不到第一个有效记录,因此可能与给定的起始位置不同。
|
||
valid_end_lsn: 当前读取的有效记录的结束位置
|
||
outputfile: 目标输出文件的指针
|
||
strOutput: 字符串缓冲区,用于记录输出信息
|
||
count: 输出记录的计数器
|
||
errormsg: 出错时的错误信息
|
||
record: XLogRecord类型的对象,表示读取的记录
|
||
*/
|
||
//将WAL(Write-Ahead Log)文件中的记录转储到指定的输出文件中
|
||
static void XLogDump(XLogRecPtr start_lsn, XLogRecPtr end_lsn, XLogFilter *filter, char *outputFilename)
|
||
{
|
||
/* start reading */
|
||
// 初始化XLogReaderState,用于读取WAL文件
|
||
errno_t rc = EOK;
|
||
XLogPrivate readprivate;
|
||
rc = memset_s(&readprivate, sizeof(XLogPrivate), 0, sizeof(XLogPrivate));
|
||
securec_check_c(rc, "\0", "\0");
|
||
readprivate.datadir = t_thrd.proc_cxt.DataDir;
|
||
readprivate.tli = 1;
|
||
XLogReaderState *xlogreader_state = XLogReaderAllocate(&SimpleXLogPageRead, &readprivate);
|
||
if (!xlogreader_state)
|
||
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
|
||
(errmsg("memory is temporarily unavailable while allocate xlog reader"))));
|
||
|
||
/* get the first valid xlog record location */
|
||
// 找到起始位置之后的第一个有效记录位置
|
||
XLogRecPtr first_record = XLogFindNextRecord(xlogreader_state, start_lsn);
|
||
/* if we are recycling or removing log files concurrently, we can't find the next record right after.
|
||
* Hence, we need to update the min_lsn */
|
||
// 如果找不到第一个有效记录,则更新min_lsn,并再次尝试查找
|
||
if (XLByteEQ(first_record, InvalidXLogRecPtr)) {
|
||
ereport(WARNING, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||
(errmsg("XLogFindNextRecord: could not find a valid record after %X/%X. Retry.",
|
||
(uint32)(start_lsn >> XIDTHIRTYTWO), (uint32)start_lsn))));
|
||
bool found = false;
|
||
first_record = UpdateNextLSN(start_lsn, end_lsn, xlogreader_state, &found);
|
||
if (!found)
|
||
ereport(ERROR,
|
||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||
(errmsg("XLogFindNextRecord: could not find a valid record between %X/%X and %X/%X.",
|
||
(uint32)(start_lsn >> XIDTHIRTYTWO), (uint32)start_lsn,
|
||
(uint32)(end_lsn >> XIDTHIRTYTWO), (uint32)end_lsn))));
|
||
}
|
||
|
||
XLogRecPtr valid_start_lsn = first_record;
|
||
XLogRecPtr valid_end_lsn = valid_start_lsn;
|
||
// 打开目标输出文件
|
||
FILE *outputfile = fopen(outputFilename, "w");
|
||
CheckOpenFile(outputfile, outputFilename);
|
||
// 输出一些转储信息
|
||
char *strOutput = (char *)palloc(MAXOUTPUTLEN * sizeof(char));
|
||
rc = memset_s(strOutput, MAXOUTPUTLEN, 0, MAXOUTPUTLEN);
|
||
securec_check(rc, "\0", "\0");
|
||
/* valid first record is not the given one */
|
||
if (!XLByteEQ(first_record, start_lsn) && (start_lsn % XLogSegSize) != 0) {
|
||
rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1,
|
||
"first record is after %X/%X, at %X/%X, skipping over %lu bytes\n", (uint32)(start_lsn >> XIDTHIRTYTWO),
|
||
(uint32)start_lsn, (uint32)(first_record >> XIDTHIRTYTWO), (uint32)first_record,
|
||
XLByteDifference(first_record, start_lsn));
|
||
securec_check_ss(rc, "\0", "\0");
|
||
}
|
||
CheckWriteFile(fwrite(strOutput, 1, strlen(strOutput), outputfile), strlen(strOutput), outputFilename);
|
||
pfree_ext(strOutput);
|
||
|
||
int count = 0;
|
||
char *errormsg = NULL;
|
||
XLogRecord *record = NULL;
|
||
// 不断读取WAL记录,直到结束位置
|
||
while (XLByteLT(xlogreader_state->EndRecPtr, end_lsn)) {
|
||
record = XLogReadRecord(xlogreader_state, first_record, &errormsg);
|
||
valid_end_lsn = xlogreader_state->EndRecPtr;
|
||
if (!record && XLByteLT(valid_end_lsn, end_lsn)) {
|
||
/* if we are recycling or removing log files concurrently, and we can't find the next record right after.
|
||
* In this case, we try to read from the current oldest xlog file. */
|
||
// 如果无法读取下一条记录,则更新min_lsn并继续尝试读取
|
||
bool found = false;
|
||
XLogRecPtr temp_start_lsn = Max(xlogreader_state->EndRecPtr, start_lsn);
|
||
first_record = UpdateNextLSN(temp_start_lsn, end_lsn, xlogreader_state, &found);
|
||
if (found) {
|
||
ereport(WARNING, (errcode(ERRCODE_WARNING),
|
||
(errmsg("We cannot read %X/%X. After retried, we jump to read the next available %X/%X. " \
|
||
"The missing part might be recycled or removed.",
|
||
(uint32)(temp_start_lsn >> XIDTHIRTYTWO), (uint32)temp_start_lsn,
|
||
(uint32)(first_record >> XIDTHIRTYTWO), (uint32)first_record))));
|
||
continue;
|
||
}
|
||
|
||
if (errormsg != NULL)
|
||
ereport(LOG,
|
||
(errcode(ERRCODE_LOG),
|
||
(errmsg("could not read WAL record at %X/%X: %s",
|
||
(uint32)(xlogreader_state->ReadRecPtr >> XIDTHIRTYTWO),
|
||
(uint32)xlogreader_state->ReadRecPtr,
|
||
errormsg))));
|
||
else
|
||
ereport(LOG,
|
||
(errcode(ERRCODE_LOG),
|
||
(errmsg("could not read WAL record at %X/%X",
|
||
(uint32)(xlogreader_state->ReadRecPtr >> XIDTHIRTYTWO),
|
||
(uint32)xlogreader_state->ReadRecPtr))));
|
||
break;
|
||
}
|
||
first_record = InvalidXLogRecPtr; /* No explicit start point; read the record after the one we just read */
|
||
// 根据过滤器筛选记录,并输出
|
||
if (filter->by_xid_enabled && filter->by_xid != record->xl_xid) {
|
||
continue;
|
||
}
|
||
|
||
if (filter->by_tablepath_enabled) { /* filter by table path */
|
||
if (!XLogRecHasAnyBlockRefs(xlogreader_state)) {
|
||
continue;
|
||
} else if (!CheckValidRecord(xlogreader_state, filter)) {
|
||
/* at least have one block ref, but not match filter */
|
||
continue;
|
||
}
|
||
}
|
||
strOutput = (char *)palloc(MAXOUTPUTLEN * sizeof(char));
|
||
rc = memset_s(strOutput, MAXOUTPUTLEN, 0, MAXOUTPUTLEN);
|
||
securec_check(rc, "\0", "\0");
|
||
|
||
XLogDumpDisplayRecord(xlogreader_state, strOutput);
|
||
count++;
|
||
|
||
CheckWriteFile(fwrite(strOutput, 1, strlen(strOutput), outputfile), strlen(strOutput), outputFilename);
|
||
pfree_ext(strOutput);
|
||
}
|
||
// 输出结束信息并关闭文件
|
||
XLogReaderFree(xlogreader_state);
|
||
strOutput = (char *)palloc(MAXOUTPUTLEN * sizeof(char));
|
||
rc = memset_s(strOutput, MAXOUTPUTLEN, 0, MAXOUTPUTLEN);
|
||
securec_check(rc, "\0", "\0");
|
||
|
||
/* Summary(xx total): valid start_lsn: xxx, valid end_lsn: xxx */
|
||
rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1,
|
||
"\nSummary (%d total): valid start_lsn: %X/%X, valid end_lsn: %X/%X\n", count,
|
||
(uint32)(valid_start_lsn >> XIDTHIRTYTWO), (uint32)(valid_start_lsn), (uint32)(valid_end_lsn >> XIDTHIRTYTWO),
|
||
(uint32)(valid_end_lsn));
|
||
securec_check_ss(rc, "\0", "\0");
|
||
/* generate output file */
|
||
CheckWriteFile(fwrite(strOutput, 1, strlen(strOutput), outputfile), strlen(strOutput), outputFilename);
|
||
CheckCloseFile(fclose(outputfile), outputFilename);
|
||
pfree_ext(strOutput);
|
||
CloseXlogFile();
|
||
}
|
||
|
||
/* There are only two parameters in PG_FUNCTION_ARGS: start_lsn and end_lsn */
|
||
/*
|
||
这是一个PostgreSQL函数"gs_xlogdump_lsn"。
|
||
它接收起始LSN和结束LSN作为输入参数,并对指定的WAL文件进行转储。
|
||
*/
|
||
Datum gs_xlogdump_lsn(PG_FUNCTION_ARGS)
|
||
{
|
||
errno_t rc = EOK;
|
||
/* check user's right */
|
||
/* 检查用户权限 */
|
||
const char fName[MAXFNAMELEN] = "gs_xlogdump_lsn";
|
||
CheckUser(fName);
|
||
|
||
/* read in parameters */
|
||
/* 读取并验证起始LSN和结束LSN */
|
||
char *start_lsn_str = text_to_cstring(PG_GETARG_TEXT_P(0));
|
||
char *end_lsn_str = text_to_cstring(PG_GETARG_TEXT_P(1));
|
||
|
||
/* validate lsn */
|
||
XLogRecPtr start_lsn, end_lsn;
|
||
ValidateStartEndLSN(start_lsn_str, end_lsn_str, &start_lsn, &end_lsn);
|
||
|
||
/*
|
||
根据起始LSN和结束LSN生成输出文件名
|
||
这里使用了GenerateOutputFileName函数
|
||
*/
|
||
|
||
char *outputFilename = (char *)palloc(MAXFILENAME * sizeof(char));
|
||
rc = memset_s(outputFilename, MAXFILENAME, 0, MAXFILENAME);
|
||
securec_check(rc, "\0", "\0");
|
||
GenerateOutputFileName(outputFilename, start_lsn_str, end_lsn_str);
|
||
|
||
/* update start_lsn and end_lsn based on cur min_lsn and max_lsn */
|
||
/*
|
||
根据当前系统中的min_lsn和max_lsn更新起始LSN和结束LSN
|
||
这是为了避免转储过程中出现不一致的情况。
|
||
*/
|
||
XLogRecPtr min_lsn = GetMinLSN();
|
||
XLogRecPtr max_lsn = GetMaxLSN();
|
||
|
||
if (XLByteLT(start_lsn, min_lsn)) {
|
||
start_lsn = min_lsn;
|
||
}
|
||
if (XLByteLT(max_lsn, end_lsn)) {
|
||
end_lsn = max_lsn;
|
||
}
|
||
/* 初始化一个XLogFilter结构体,用于筛选日志记录 */
|
||
XLogFilter filter;
|
||
rc = memset_s(&filter, sizeof(XLogFilter), 0, sizeof(XLogFilter));
|
||
securec_check_ss(rc, "\0", "\0");
|
||
/* 对指定的WAL文件进行转储 */
|
||
XLogDump(start_lsn, end_lsn, &filter, outputFilename);
|
||
/* 返回输出文件名 */
|
||
PG_RETURN_TEXT_P(cstring_to_text(outputFilename));
|
||
}
|
||
|
||
/* There are only one parameter in PG_FUNCTION_ARGS: c_xid */
|
||
//基于事务ID对WAL文件进行转储
|
||
Datum gs_xlogdump_xid(PG_FUNCTION_ARGS)
|
||
{
|
||
errno_t rc = EOK;
|
||
/* check user's right */
|
||
/* 检查用户是否有权限调用该函数 */
|
||
const char fName[MAXFNAMELEN] = "gs_xlogdump_xid";
|
||
CheckUser(fName);
|
||
|
||
/* read in parameters */
|
||
/* 读取并验证传入的事务ID */
|
||
TransactionId c_xid = PG_GETARG_TRANSACTIONID(0);
|
||
/* check parameters */
|
||
/* 获取系统当前的最大事务ID,以确保传入的事务ID有效 */
|
||
TransactionId topXid = GetTopTransactionId();
|
||
if (TransactionIdPrecedes(topXid, c_xid))
|
||
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||
(errmsg("Xid should less than or equal to %lu.", topXid))));
|
||
|
||
/* generate output file name */
|
||
/* 生成输出文件名 */
|
||
char *outputFilename = (char *)palloc(MAXFILENAME * sizeof(char));
|
||
rc = memset_s(outputFilename, MAXFILENAME, 0, MAXFILENAME);
|
||
securec_check(rc, "\0", "\0");
|
||
rc = snprintf_s(outputFilename + (int)strlen(outputFilename), MAXFILENAME, MAXFILENAME - 1, "%s/%lu.xlog",
|
||
t_thrd.proc_cxt.DataDir, c_xid);
|
||
securec_check_ss(rc, "\0", "\0");
|
||
/* update start_lsn and end_lsn based on cur min_lsn and max_lsn */
|
||
/* 获取最小LSN和最大LSN,并更新转储范围 */
|
||
XLogRecPtr min_lsn = GetMinLSN();
|
||
XLogRecPtr max_lsn = GetMaxLSN();
|
||
/* 初始化XLogFilter结构体,设定需要根据事务ID来筛选记录 */
|
||
XLogFilter filter;
|
||
rc = memset_s(&filter, sizeof(XLogFilter), 0, sizeof(XLogFilter));
|
||
securec_check(rc, "\0", "\0");
|
||
filter.by_xid_enabled = true;
|
||
filter.by_xid = c_xid;
|
||
/* 调用XLogDump函数进行转储 */
|
||
XLogDump(min_lsn, max_lsn, &filter, outputFilename);
|
||
/* 返回输出文件名 */
|
||
PG_RETURN_TEXT_P(cstring_to_text(outputFilename));
|
||
}
|
||
//用于将指定路径和块号(或所有块)下的 WAL 日志记录转储到输出文件中
|
||
/* There are only three parameters in PG_FUNCTION_ARGS: path, blocknum, relation_type */
|
||
Datum gs_xlogdump_tablepath(PG_FUNCTION_ARGS)
|
||
{
|
||
errno_t rc = EOK;
|
||
/* check user's right */
|
||
//查用户是否有执行该函数的权限
|
||
const char fName[MAXFNAMELEN] = "gs_xlogdump_tablepath";
|
||
CheckUser(fName);
|
||
|
||
/* read in parameters */
|
||
char *path = text_to_cstring(PG_GETARG_TEXT_P(0));
|
||
int64 blocknum = PG_GETARG_INT64(1);
|
||
char *relation_type = text_to_cstring(PG_GETARG_TEXT_P(2));
|
||
/* check parameters */
|
||
//检查块号是否在合法范围内(-1 到 MaxBlockNumber 之间),如果不在范围内,则抛出错误
|
||
if (blocknum > MaxBlockNumber || blocknum < -1)
|
||
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||
(errmsg("Blocknum should be between -1 and %u.", MaxBlockNumber))));
|
||
|
||
/* update start_lsn and end_lsn based on cur min_lsn and max_lsn */
|
||
XLogRecPtr min_lsn = GetMinLSN();
|
||
XLogRecPtr max_lsn = GetMaxLSN();
|
||
XLogFilter filter;
|
||
rc = memset_s(&filter, sizeof(XLogFilter), 0, sizeof(XLogFilter));
|
||
securec_check_c(rc, "\0", "\0");
|
||
filter.by_tablepath_enabled = true;
|
||
|
||
if (blocknum == -1) { /* care all blocks */ //示处理所有块
|
||
filter.by_block = false;
|
||
} else { /* only one block */
|
||
filter.by_block = true;
|
||
filter.blocknum = blocknum;
|
||
}
|
||
|
||
/* generate output file name */
|
||
char *outputFilename = (char *)palloc(MAXFILENAME * sizeof(char));
|
||
rc = memset_s(outputFilename, MAXFILENAME, 0, MAXFILENAME);//确保输出文件名为空字符串
|
||
securec_check(rc, "\0", "\0");
|
||
PrepForRead(path, blocknum, relation_type, outputFilename, &(filter.by_relfilenode), false);//根据 filter.by_relfilenode验证路径和关联关系类型参数的合法性
|
||
ValidateParameterPath(filter.by_relfilenode, path);
|
||
//执行转储操作
|
||
XLogDump(min_lsn, max_lsn, &filter, outputFilename);
|
||
|
||
PG_RETURN_TEXT_P(cstring_to_text(outputFilename));//将输出文件名转换为 PostgreSQL 文本类型
|
||
}
|
||
//解析和转储数据库中的WAL(Write-Ahead Log)日志记录和数据页
|
||
Datum gs_xlogdump_parsepage_tablepath(PG_FUNCTION_ARGS)
|
||
{
|
||
/* check user's right */
|
||
//检查用户权限
|
||
const char fName[MAXFNAMELEN] = "gs_xlogdump_parsepage_tablepath";
|
||
CheckUser(fName);
|
||
|
||
/* read in parameters */
|
||
char *path = text_to_cstring(PG_GETARG_TEXT_P(0));
|
||
int64 blocknum = PG_GETARG_INT64(1);
|
||
char *relation_type = text_to_cstring(PG_GETARG_TEXT_P(2));
|
||
bool read_memory = PG_GETARG_BOOL(3);
|
||
int rc = -1;
|
||
|
||
/* page part, copy path since for segment we will edit path */
|
||
//读取传入的函数参数,并进行相应的数据类型转换
|
||
char *path_cpy = (char *)palloc(MAXFILENAME + 1);
|
||
rc = strcpy_s(path_cpy, MAXFILENAME + 1, path);
|
||
securec_check(rc, "\0", "\0");
|
||
|
||
/* In order to avoid querying the shared buffer and applying LW locks, blocking the business. */
|
||
/* In the case of finding all pages, force to check disk */
|
||
if (blocknum == -1) {
|
||
read_memory = false;//强制从磁盘读取数据
|
||
}
|
||
char *outputFilenamePage = ParsePage(path_cpy, blocknum, relation_type, read_memory);
|
||
pfree_ext(path_cpy);
|
||
/* xlog part */
|
||
/* update start_lsn and end_lsn based on cur min_lsn and max_lsn */
|
||
XLogFilter filter;
|
||
rc = memset_s(&filter, sizeof(XLogFilter), 0, sizeof(XLogFilter));
|
||
securec_check_c(rc, "\0", "\0");
|
||
|
||
if (blocknum == -1) { /* care all blocks */
|
||
filter.by_block = false;
|
||
} else { /* only one block */
|
||
filter.by_block = true;
|
||
filter.blocknum = blocknum;
|
||
}
|
||
filter.by_tablepath_enabled = true;
|
||
/* generate output file name */
|
||
char *outputFilename = (char *)palloc(MAXFILENAME * sizeof(char));
|
||
rc = memset_s(outputFilename, MAXFILENAME, 0, MAXFILENAME);
|
||
securec_check(rc, "\0", "\0");
|
||
PrepForRead(path, blocknum, relation_type, outputFilename, &(filter.by_relfilenode), false);
|
||
//获取最小和最大 LSN 并调用 XLogDump 函数将符合筛选条件的 WAL 日志记录转储到输出文件中。
|
||
XLogRecPtr min_lsn = GetMinLSN();
|
||
XLogRecPtr max_lsn = GetMaxLSN();
|
||
XLogDump(min_lsn, max_lsn, &filter, outputFilename);
|
||
//计算生成结果字符串所需的长度,并为结果字符串分配内存
|
||
int outputLen = strlen(outputFilename) + strlen(outputFilenamePage) + 100;
|
||
if (outputLen <= 0)
|
||
ereport(ERROR, (errcode(ERRCODE_IO_ERROR), (errmsg("Cannot generate right output file name."))));
|
||
char *result = (char *)palloc(outputLen * sizeof(char));
|
||
rc = memset_s(result, outputLen, 0, outputLen);
|
||
securec_check(rc, "\0", "\0");
|
||
|
||
rc = snprintf_s(result + (int)strlen(result), outputLen, outputLen - 1,
|
||
"Output file for parsing xlog: %s\nOutput file for parsing data page: %s", outputFilename, outputFilenamePage);
|
||
securec_check_ss(rc, "\0", "\0");
|
||
pfree_ext(outputFilename);
|
||
PG_RETURN_TEXT_P(cstring_to_text(result));
|
||
}
|