diff --git a/src/gausskernel/storage/bulkload/dist_fdw.cpp b/src/gausskernel/storage/bulkload/dist_fdw.cpp index 44155d30f..b692ddc5e 100644 --- a/src/gausskernel/storage/bulkload/dist_fdw.cpp +++ b/src/gausskernel/storage/bulkload/dist_fdw.cpp @@ -129,50 +129,49 @@ const int GSOBS_PREFIX_LEN = strlen(GSOBS_PREFIX); */ static struct DistFdwOption loader_valid_options[] = { /* File options */ - { optLocation, ForeignTableRelationId }, - { OPTION_NAME_REGION, ForeignTableRelationId }, + {optLocation, ForeignTableRelationId}, + {OPTION_NAME_REGION, ForeignTableRelationId}, /* Format options */ /* oids option is not supported */ - { optFormat, ForeignTableRelationId }, - { optHeader, ForeignTableRelationId }, - { optDelimiter, ForeignTableRelationId }, - { optQutote, ForeignTableRelationId }, - { optEscape, ForeignTableRelationId }, - { optNull, ForeignTableRelationId }, - { optEncoding, ForeignTableRelationId }, - { optFillMissFields, ForeignTableRelationId }, - { optMode, ForeignTableRelationId }, - { optWithoutEscaping, ForeignTableRelationId }, - { optForceNotNull, AttributeRelationId }, - { optEol, ForeignTableRelationId }, - { optFix, ForeignTableRelationId }, - { optFileHeader, ForeignTableRelationId }, - { optOutputFilePrefix, ForeignTableRelationId }, - { optOutputFixAlignment, ForeignTableRelationId }, - { optRejectLimit, ForeignTableRelationId }, - { optIgnoreExtraData, ForeignTableRelationId }, + {optFormat, ForeignTableRelationId}, + {optHeader, ForeignTableRelationId}, + {optDelimiter, ForeignTableRelationId}, + {optQutote, ForeignTableRelationId}, + {optEscape, ForeignTableRelationId}, + {optNull, ForeignTableRelationId}, + {optEncoding, ForeignTableRelationId}, + {optFillMissFields, ForeignTableRelationId}, + {optMode, ForeignTableRelationId}, + {optWithoutEscaping, ForeignTableRelationId}, + {optForceNotNull, AttributeRelationId}, + {optEol, ForeignTableRelationId}, + {optFix, ForeignTableRelationId}, + {optFileHeader, ForeignTableRelationId}, + {optOutputFilePrefix, ForeignTableRelationId}, + {optOutputFixAlignment, ForeignTableRelationId}, + {optRejectLimit, ForeignTableRelationId}, + {optIgnoreExtraData, ForeignTableRelationId}, /* OBS only options */ - { optChunkSize, ForeignTableRelationId }, - { optEncrypt, ForeignTableRelationId }, - { optAccessKey, ForeignTableRelationId }, - { optSecretAccessKey, ForeignTableRelationId }, + {optChunkSize, ForeignTableRelationId}, + {optEncrypt, ForeignTableRelationId}, + {optAccessKey, ForeignTableRelationId}, + {optSecretAccessKey, ForeignTableRelationId}, /* * bulkload compatible illegal chars option */ - { optCompatibleIllegalChars, ForeignTableRelationId }, + {optCompatibleIllegalChars, ForeignTableRelationId}, /* * bulkload datetime format options */ - { optDateFormat, ForeignTableRelationId }, - { optTimeFormat, ForeignTableRelationId }, - { optTimestampFormat, ForeignTableRelationId }, - { optSmalldatetimeFormat, ForeignTableRelationId }, + {optDateFormat, ForeignTableRelationId}, + {optTimeFormat, ForeignTableRelationId}, + {optTimestampFormat, ForeignTableRelationId}, + {optSmalldatetimeFormat, ForeignTableRelationId}, /* Sentinel */ - { NULL, InvalidOid } -}; + {NULL, InvalidOid}}; PG_FUNCTION_INFO_V1(dist_fdw_handler); PG_FUNCTION_INFO_V1(dist_fdw_validator); @@ -297,48 +296,113 @@ Datum dist_fdw_handler(PG_FUNCTION_ARGS) * * Raise an ERROR if the option or its value is considered invalid. */ +/* + * 功能:分布式外部数据导入的选项验证函数 + * + * 参数列表: + * PG_FUNCTION_ARGS:PostgreSQL UDF 函数参数 + * + * 返回值: + * 无 + */ Datum dist_fdw_validator(PG_FUNCTION_ARGS) { + // 解析外部表选项列表 List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); + + // 获取外部表的目录 OID Oid catalog = PG_GETARG_OID(1); + + // 创建分布式导入计划状态和执行状态结构 DistImportPlanState planstate; DistImportExecutionState execState; - errno_t rc = EOK; + // 初始化计划状态结构的内存 + errno_t rc = EOK; rc = memset_s(&planstate, sizeof(planstate), 0, sizeof(planstate)); securec_check(rc, "\0", "\0"); + + // 初始化执行状态结构的内存 rc = memset_s(&execState, sizeof(execState), 0, sizeof(execState)); securec_check(rc, "\0", "\0"); + + // 处理外部表选项,填充计划状态结构 ProcessDistImportOptions(&planstate, options_list, true, catalog != ForeignTableRelationId); - ProcessCopyOptions((CopyState) & execState, !planstate.writeOnly, planstate.options); + + // 处理COPY选项,填充执行状态结构 + ProcessCopyOptions((CopyState)&execState, !planstate.writeOnly, planstate.options); + + // 验证文件编码 VerifyEncoding(planstate.fileEncoding); + + // 如果文件格式不是TEXT并且使用了SHARED模式,则报错 if (execState.fileformat != FORMAT_TEXT && IS_SHARED_MODE(planstate.mode)) ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("SHARED mode can only be used with TEXT format"))); + + // 返回空结果 PG_RETURN_VOID(); } +/* + * 功能:获取外部错误记录表的缓存条目 + * + * 参数列表: + * relid:外部表的OID + * distSessionKey:分布式会话密钥 + * + * 返回值: + * 如果找到相关选项并成功获取ErrorCacheEntry,返回该条目的指针,否则返回NULL。 + */ ErrorCacheEntry *GetForeignErrCacheEntry(Oid relid, uint32 distSessionKey) { + // 声明并初始化一个指向ErrorCacheEntry结构的指针entry,初始值为NULL ErrorCacheEntry *entry = NULL; + + // 使用GetForeignTableOptionByName函数查找与relid相关的外部表选项optErrorRel DefElem *def = GetForeignTableOptionByName(relid, optErrorRel); + // 如果找到了选项def if (def != NULL) { + // 声明并初始化一个指向RangeTblEntry结构的指针rte,初始值为NULL RangeTblEntry *rte = NULL; + + // 从选项def中获取外部错误记录表的表名relname char *relname = strVal(def->arg); + + // 使用表名relname和关联表的命名空间获取错误记录表的OID Oid errorOid = get_relname_relid(relname, get_rel_namespace(relid)); + // 创建一个新的ErrorCacheEntry结构,并将其分配给entry entry = makeNode(ErrorCacheEntry); + + // 如果找到的errorOid是无效的 if (errorOid == InvalidOid) + // 报告一个错误,指明无法找到错误记录表 ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("can't find error record table \"%s\"", relname))); + + // 为entry设置filename,生成唯一的缓存名称前缀,使用errorOid和distSessionKey entry->filename = generate_unique_cache_name_prefix(errorOid, distSessionKey); + // 分配并初始化一个新的RangeTblEntry结构,并将其分配给rte rte = (RangeTblEntry *)makeNode(RangeTblEntry); + + // 设置rte的类型为RTE_RELATION,表示这是一个关系表 rte->rtekind = RTE_RELATION; + + // 设置rte的relid为errorOid,表示关联的表的OID rte->relid = errorOid; + + // 获取并设置rte的relkind,表示关联表的类型(如表、视图等) rte->relkind = get_rel_relkind(errorOid); + + // 设置rte的requiredPerms为ACL_INSERT,表示所需的权限是插入权限 rte->requiredPerms = ACL_INSERT; + + // 将rte设置为entry的rte字段,表示与错误记录表关联的RangeTblEntry entry->rte = rte; } + + // 返回获取的ErrorCacheEntry结构指针,如果没有找到相关的选项,则为NULL return entry; } @@ -346,14 +410,28 @@ ErrorCacheEntry *GetForeignErrCacheEntry(Oid relid, uint32 distSessionKey) * Check if the provided option is one of the valid options. * context is the Oid of the catalog holding the object the option is for. */ +/* + * 功能:检查选项是否在有效选项列表中 + * + * 参数列表: + * option:要检查的选项名称 + * context:选项的上下文(例如,ForeignTableRelationId) + * + * 返回值: + * 如果选项在有效选项列表中且上下文匹配,返回true;否则返回false。 + */ static bool is_valid_option(const char *option, Oid context) { struct DistFdwOption *opt = NULL; + // 遍历有效选项列表 for (opt = loader_valid_options; opt->optname; opt++) { + // 检查选项上下文和选项名称是否匹配 if (context == opt->optcontext && strcmp(opt->optname, option) == 0) return true; } + + // 未找到匹配的选项 return false; } @@ -363,6 +441,17 @@ static bool is_valid_option(const char *option, Oid context) * We have to separate out "filename" from the other options because * it must not appear in the options list passed to the core COPY code. */ +/* + * 功能:从外部表、外部服务器和外部数据包装器中提取选项 + * + * 参数列表: + * foreigntableid:外部表的OID + * locaionts:外部表的LOCATION选项值(输出参数) + * other_options:包含除LOCATION以外的其他选项的选项列表(输出参数) + * + * 返回值: + * 无 + */ void distGetOptions(Oid foreigntableid, char **locaionts, List **other_options) { ForeignTable *table = NULL; @@ -415,19 +504,38 @@ void distGetOptions(Oid foreigntableid, char **locaionts, List **other_options) * we add the parameter (void* additionalData). This parameter may not be * used by fileAnalyzeForeignTable function. */ +/* + * 功能:分析外部表的函数 + * + * 参数列表: + * relation:关系对象 + * func:采集样本行的函数指针 + * totalPageCount:总页数(输出参数) + * additionalData:额外的分析数据(通常是外部表的相关信息) + * estimate_table_rownum:是否估算表的行数 + * + * 返回值: + * 如果成功分析外部表,返回true;否则返回false。 + */ static bool distAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNumber *totalPageCount, void *additionalData, bool estimate_table_rownum) { + // 如果外部表是只写的,不进行分析 if (isWriteOnlyFt(RelationGetRelid(relation))) { return false; } + + // 如果外部表不是OBS CSV或TXT格式,不进行分析 if (!IS_OBS_CSV_TXT_FOREIGN_TABLE(RelationGetRelid(relation))) { return false; } - if (additionalData == NULL) + // 如果additionalData为NULL,表示不需要进行分析,直接返回true + if (additionalData == NULL) { return true; + } + // 获取外部表的总页数,并将结果存储在totalPageCount中 (*totalPageCount) = getPageCountForFt(additionalData); return true; @@ -439,31 +547,54 @@ static bool distAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *fu * @in foreignTableId, the given foreign table Oid. * @return return the dn task. */ +/* + * 功能:为分布式OBS外部表进行调度 + * + * 参数列表: + * foreignTableId:外部表的OID + * + * 返回值: + * 如果成功,返回任务列表;否则返回NULL。 + */ List *CNSchedulingForDistOBSFt(Oid foreignTableId) { + // 获取外部表的URL char *url = HdfsGetOptionValue(foreignTableId, optLocation); + // 获取外部表的Region Code char *regionCode = HdfsGetOptionValue(foreignTableId, OPTION_NAME_REGION); + // 初始化新的URL char *newUrl = url; errno_t rc = EOK; + // 断言URL不为空 Assert(url != NULL); + // 如果URL以OBS_PREFIX开头,尝试重建所有Location选项 if (url != NULL && pg_strncasecmp(url, OBS_PREFIX, OBS_PREfIX_LEN) == 0) { /* the regionCode may be NULL, we will get the default region. */ newUrl = rebuildAllLocationOptions(regionCode, url); } + // 反序列化URL列表 List *urlList = DeserializeLocations(newUrl); + // 初始化加密标志 bool encrypt = false; + // 获取服务器加密选项 DefElem *encryptStr = HdfsGetOptionDefElem(foreignTableId, OPTION_NAME_SERVER_ENCRYPT); + // 获取Access Key char *ak = HdfsGetOptionValue(foreignTableId, OPTION_NAME_SERVER_AK); + // 获取Secret Access Key char *sak = HdfsGetOptionValue(foreignTableId, OPTION_NAME_SERVER_SAK); + // 如果存在加密选项,获取加密标志 if (encryptStr != NULL) { encrypt = defGetBoolean(encryptStr); } + // 获取OBS文件列表 List *obsFileList = getOBSFileList(urlList, encrypt, ak, sak, true); + // 初始化任务列表 List *totalTask = NIL; + // 获取外部表的分布信息 List *nodeList = NIL; RelationLocInfo *rlc = GetRelationLocInfo(foreignTableId); if (rlc != NULL) { @@ -471,17 +602,22 @@ List *CNSchedulingForDistOBSFt(Oid foreignTableId) } /* get all data node names */ + // 获取所有数据节点名称 List *dnNames = !nodeList ? PgxcNodeGetAllDataNodeNames() : PgxcNodeGetDataNodeNames(nodeList); /* assign obs file to each data node */ + // 将OBS文件分配给每个数据节点 assignOBSFileToDataNode(obsFileList, &totalTask, dnNames); + // 释放关系信息 pfree(rlc); + // 清除并释放Secret Access Key的内存 if (sak != NULL) { rc = memset_s(sak, strlen(sak), 0, strlen(sak)); securec_check(rc, "\0", "\0"); pfree(sak); } + // 返回任务列表 return totalTask; } #endif @@ -494,6 +630,16 @@ List *CNSchedulingForDistOBSFt(Oid foreignTableId) * @out * @return */ +/* + * 功能:构建与外部表关联的扫描状态信息 + * + * 参数列表: + * relation:关联的外部表 + * splitinfo:分布式外部表文件段信息 + * + * 返回值: + * 返回构建的ForeignScanState对象。 + */ ForeignScanState *buildRelatedStateInfo(Relation relation, DistFdwFileSegment *splitinfo) { ForeignScanState *scanState = NULL; @@ -505,29 +651,36 @@ ForeignScanState *buildRelatedStateInfo(Relation relation, DistFdwFileSegment *s TupleDesc tupleDescriptor = RelationGetDescr(relation); int columnCount = tupleDescriptor->natts; + // 为列值和空值分配内存 Datum *columnValues = (Datum *)palloc0(columnCount * sizeof(Datum)); bool *columnNulls = (bool *)palloc0(columnCount * sizeof(bool)); + // 创建文件段信息 fileInfo = makeNode(DistFdwFileSegment); fileInfo->filename = splitinfo->filename; + // 创建数据节点任务 DistFdwDataNodeTask *fileSplitTask = NULL; /* Put file information into SplitMap struct */ + // 将文件信息放入数据节点任务 fileSplitTask = makeNode(DistFdwDataNodeTask); fileWorkList = lappend(fileWorkList, (void *)fileInfo); fileSplitTask->dnName = g_instance.attr.attr_common.PGXCNodeName; fileSplitTask->task = fileWorkList; + // 将数据节点任务添加到HDFS节点工作列表 HDFSNodeWorkList = lappend(HDFSNodeWorkList, fileSplitTask); /* setup foreign scan plan node */ + // 设置外部扫描计划节点 ForeignScan *foreignScan = NULL; foreignScan = makeNode(ForeignScan); - foreignScan->fdw_private = lappend(foreignScan->fdw_private, - makeDefElem(pstrdup(optTaskList), (Node *)HDFSNodeWorkList)); + foreignScan->fdw_private = + lappend(foreignScan->fdw_private, makeDefElem(pstrdup(optTaskList), (Node *)HDFSNodeWorkList)); ; /* setup tuple slot */ + // 设置元组槽 scanTupleSlot = MakeTupleTableSlot(true, tupleDescriptor->tdTableAmType); scanTupleSlot->tts_tupleDescriptor = tupleDescriptor; scanTupleSlot->tts_values = columnValues; @@ -539,10 +692,12 @@ ForeignScanState *buildRelatedStateInfo(Relation relation, DistFdwFileSegment *s scanState->ss.ps.plan = (Plan *)foreignScan; scanState->ss.ss_ScanTupleSlot = scanTupleSlot; - scanState->scanMcxt = AllocSetContextCreate(CurrentMemoryContext, "analyze for Foreign Scan", - ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); + // 分配扫描内存上下文 + scanState->scanMcxt = + AllocSetContextCreate(CurrentMemoryContext, "analyze for Foreign Scan", ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + // 开始分布式导入操作 distImportBegin(scanState, 0); return scanState; @@ -570,7 +725,8 @@ ForeignScanState *buildRelatedStateInfo(Relation relation, DistFdwFileSegment *s * input param @additionalData:we use this parameter to pass data. */ static int distAcquireSampleRows(Relation relation, int logLevel, HeapTuple *sampleRows, int targetRowCount, - double *totalRowCount, double *deadRows, void *additionalData, bool estimate_table_rownum) + double *totalRowCount, double *deadRows, void *additionalData, + bool estimate_table_rownum) { /* We report "analyze" nothing if additionalData is null. */ if (additionalData == NULL) { @@ -613,9 +769,9 @@ static int distAcquireSampleRows(Relation relation, int logLevel, HeapTuple *sam * Use per-tuple memory context to prevent leak of memory used to read and * parse rows from the file using ReadLineFromFile and FillTupleSlot. */ - tupleContext = AllocSetContextCreate(CurrentMemoryContext, "TEX/CSV OBS temporary context", - ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); + tupleContext = + AllocSetContextCreate(CurrentMemoryContext, "TEX/CSV OBS temporary context", ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); /* prepare for sampling rows */ selectionState = anl_init_selection_state(targetRowCount); @@ -655,7 +811,8 @@ static int distAcquireSampleRows(Relation relation, int logLevel, HeapTuple *sam * reach the end of the relation. */ if (sampleRowCount < targetRowCount) { - sampleRows[sampleRowCount++] = (HeapTuple)tableam_tops_form_tuple(tupleDescriptor, columnValues, columnNulls, HEAP_TUPLE); + sampleRows[sampleRowCount++] = + (HeapTuple)tableam_tops_form_tuple(tupleDescriptor, columnValues, columnNulls, HEAP_TUPLE); } else { /* * If we need to compute a new S value, we must use the "not yet @@ -675,7 +832,8 @@ static int distAcquireSampleRows(Relation relation, int logLevel, HeapTuple *sam Assert(rowIndex < targetRowCount); heap_freetuple(sampleRows[rowIndex]); - sampleRows[rowIndex] = (HeapTuple)tableam_tops_form_tuple(tupleDescriptor, columnValues, columnNulls, HEAP_TUPLE); + sampleRows[rowIndex] = + (HeapTuple)tableam_tops_form_tuple(tupleDescriptor, columnValues, columnNulls, HEAP_TUPLE); } rowCountToSkip -= 1; } @@ -719,7 +877,17 @@ static int distAcquireSampleRows(Relation relation, int logLevel, HeapTuple *sam (*deadRows) = 0; /* @hdfs dead rows is no means to foreign table */ return sampleRowCount; } - +/* + * 功能:检查是否需要选择性的二进制转换 + * + * 参数列表: + * baserel:基本关系信息 + * foreigntableid:外部表的OID + * columns:需要选择性转换的列名列表(输出参数) + * + * 返回值: + * 如果需要进行选择性二进制转换,返回true;否则返回false。 + */ bool check_selective_binary_conversion(RelOptInfo *baserel, Oid foreigntableid, List **columns) { ForeignTable *table = NULL; @@ -819,6 +987,19 @@ bool check_selective_binary_conversion(RelOptInfo *baserel, Oid foreigntableid, /* * read raw buffer */ +/* + * 功能:从文件中读取原始数据到缓冲区 + * + * 参数列表: + * file:文件指针 + * buf:用于存储读取的数据的缓冲区 + * pos:文件中的位置,从该位置开始读取数据 + * len:要读取的数据长度 + * filename:文件名(用于错误处理) + * + * 返回值: + * 返回读取的字节数,如果发生错误,则抛出错误并终止程序。 + */ static int getRawBuffer(FILE *file, char *buf, long pos, long len, char *filename) { int bytesread = 0; @@ -831,13 +1012,27 @@ static int getRawBuffer(FILE *file, char *buf, long pos, long len, char *filenam ereport(ERROR, (errcode_for_file_access(), errmsg("could not read from file: %m"))); } - Assert(bytesread == len); + Assert(bytesread == len); // 确保读取的字节数等于请求的长度 return bytesread; } /* * search char from begin to end */ +/* + * 功能:在文件中向前搜索指定字符 + * + * 参数列表: + * file:文件指针 + * buf:用于存储读取的数据的缓冲区 + * begin:搜索范围的起始位置 + * end:搜索范围的结束位置 + * fileName:文件名(用于错误处理) + * searchChar:要搜索的字符 + * + * 返回值: + * 如果找到指定字符,返回其在文件中的位置;否则返回0。 + */ static long searchForward(FILE *file, char *buf, long begin, long end, char *fileName, char searchChar) { long readStartPos = end; @@ -873,6 +1068,18 @@ static long searchForward(FILE *file, char *buf, long begin, long end, char *fil /* * Divide file into segments */ +/* + * 功能:将文件分割成多个文件段 + * + * 参数列表: + * fileName:要分割的文件名 + * fileSize:文件的总大小 + * segmentlist:存储文件段的列表(输出参数) + * + * 注意: + * 该函数将指定的文件分割成多个文件段,每个文件段都包含文件的一部分。 + * 文件段列表将作为输出参数返回。 + */ void divideFileSegment(char *fileName, long fileSize, List **segmentlist) { Assert(fileName && segmentlist && fileSize >= 0); @@ -965,6 +1172,15 @@ void divideFileSegment(char *fileName, long fileSize, List **segmentlist) /* * Through each directory to find the matching files, and segment it if the file large than 64MB */ +/* + * 功能:获取文件段列表 + * + * 参数列表: + * urllist:包含文件URL的列表 + * + * 返回值: + * 返回一个包含文件段的列表。 + */ List *getFileSegmentList(List *urllist) { char *path = NULL; @@ -1001,6 +1217,16 @@ List *getFileSegmentList(List *urllist) /* * assign file segments to data node */ +/* + * 功能:将文件段列表分配给数据节点任务列表 + * + * 参数列表: + * segmentlist:文件段列表 + * dnNames:数据节点名称列表 + * + * 返回值: + * 返回包含数据节点任务的列表。 + */ List *assignFileSegmentList(List *segmentlist, List *dnNames) { ListCell *lc = NULL; @@ -1333,7 +1559,15 @@ List *assignTaskToDataNode(List *urllist, ImportMode mode, List *nodeList, int d return totalTask; } - +/* + * 功能:决定是否接受一个错误记录 + * + * 参数列表: + * festate:分布式导入执行状态 + * + * 返回值: + * 如果需要保存错误记录且尚未达到拒绝限制,则返回true,否则返回false。 + */ bool DoAcceptOneError(DistImportExecutionState *festate) { bool do_accept = false; @@ -1345,7 +1579,15 @@ bool DoAcceptOneError(DistImportExecutionState *festate) } return do_accept; } - +/* + * 功能:决定是否接受一个错误记录 + * + * 参数列表: + * cstate:COPY操作的状态信息 + * + * 返回值: + * 如果需要记录错误信息(log_errors或logErrorsData为true)且尚未达到拒绝限制,则返回true,否则返回false。 + */ bool DoAcceptOneError(CopyState cstate) { bool do_accept = false; @@ -1358,7 +1600,18 @@ bool DoAcceptOneError(CopyState cstate) } return do_accept; } - +/* + * 功能:初始化COPY操作的状态信息 + * + * 参数列表: + * cstate:COPY操作的状态信息 + * isImport:指示是否是导入操作 + * rel:与COPY操作关联的关系对象 + * raw_query:查询树中的原始节点 + * queryString:包含COPY操作的原始查询字符串 + * attnamelist:需要处理的属性名称列表 + * options:COPY操作的选项列表 + */ static void DistBegin(CopyState cstate, bool isImport, Relation rel, Node *raw_query, const char *queryString, List *attnamelist, List *options) { @@ -1460,8 +1713,8 @@ static void DistBegin(CopyState cstate, bool isImport, Relation rel, Node *raw_q * are the same, we must apply pg_any_to_server() to validate data in * multibyte encodings. */ - cstate->need_transcoding = (cstate->file_encoding != GetDatabaseEncoding() || - pg_database_encoding_max_length() > 1); + cstate->need_transcoding = + (cstate->file_encoding != GetDatabaseEncoding() || pg_database_encoding_max_length() > 1); /* See Multibyte encoding comment above */ cstate->encoding_embeds_ascii = PG_ENCODING_IS_CLIENT_ONLY(cstate->file_encoding); @@ -1470,7 +1723,17 @@ static void DistBegin(CopyState cstate, bool isImport, Relation rel, Node *raw_q (void)MemoryContextSwitchTo(oldcontext); } - +/* + * 功能:初始化分布式导入操作的状态信息 + * + * 参数列表: + * importstate:分布式导入操作的状态信息 + * rel:与导入操作关联的关系对象 + * filename:导入的文件名 + * attnamelist:需要处理的属性名称列表 + * options:导入操作的选项列表 + * totalTask:总的任务列表 + */ void InitDistImport(DistImportExecutionState *importstate, Relation rel, const char *filename, List *attnamelist, List *options, List *totalTask) { @@ -1652,9 +1915,9 @@ static const char *FetchAndCheckFormat(DefElem *defel); static void distExportSwitchSegment(CopyState cstate, Relation rel); #ifndef WIN32 - static const char delimiter = '/'; +static const char delimiter = '/'; #else - static const char delimiter = '\\'; +static const char delimiter = '\\'; #endif static const uint64 distExportMaxSegSize = (1 << 30); @@ -1663,9 +1926,20 @@ static const uint64 distExportMaxSegSize = (1 << 30); * distExportRelUpdatable * Determine whether a foreign table supports INSERT, UPDATE and/or DELETE. */ +/* + * 功能:检查分布式外部表的可更新性 + * + * 参数列表: + * rel:要检查的关系对象 + * + * 返回值: + * 返回表示可更新性的位掩码,根据可用的命令而定 + * 位掩码为 CMD_INSERT,表示可以执行 INSERT 操作 + * 如果关系对象不是分布式外部表或者是只写模式,返回 0,表示不可更新 + */ static int distExportRelUpdatable(Relation rel) { - // for dist foreign talbe, only checking table's permition is enough + // 对于分布式外部表,仅检查表的权限即可 ForeignTable *table = GetForeignTable(RelationGetRelid(rel)); if (table != NULL && table->write_only) return (1 << CMD_INSERT); @@ -1677,61 +1951,109 @@ static int distExportRelUpdatable(Relation rel) * distExportPlan * Plan an INSERT operation on a foreign table */ +/* + * 功能:生成分布式导入计划 + * + * 参数列表: + * root:PlannerInfo 对象,用于查询规划的信息 + * plan:ModifyTable 计划节点,表示修改表的操作 + * resultRelation:索引表示结果关系的位置 + * subplan_index:子计划的索引 + * + * 返回值: + * 返回一个包含 FDW 私有数据的列表 + * 如果操作是更新 (CMD_UPDATE) 或删除 (CMD_DELETE),返回一个空列表 (NIL) + * 如果操作是插入 (CMD_INSERT) 并且位置不在本地,则返回一个包含会话密钥和任务列表的列表 + */ static List *distExportPlan(PlannerInfo *root, ModifyTable *plan, Index resultRelation, int subplan_index) { - CmdType operation = plan->operation; - List *fdwPriv = NIL; - DistImportPlanState *planstate = NULL; - RangeTblEntry *rte = NULL; - char *location = NULL; + CmdType operation = plan->operation; // 获取操作类型 + List *fdwPriv = NIL; // 用于存储 FDW 私有数据的列表 + DistImportPlanState *planstate = NULL; // 分布式导入计划的状态 + RangeTblEntry *rte = NULL; // 范围表条目 + char *location = NULL; // 外部表的位置 + // 如果不是流式计划,则抛出错误 if (!IS_STREAM_PLAN) - ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("Un-support feature"))); + ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("Unsupported feature"))); + // 如果操作是更新 (CMD_UPDATE) 或删除 (CMD_DELETE),则返回空列表 if (CMD_UPDATE == operation || CMD_DELETE == operation) return NIL; + // 断言操作是插入 (CMD_INSERT) Assert(CMD_INSERT == operation); + + // 获取结果关系的范围表条目 rte = planner_rt_fetch(resultRelation, root); + + // 创建 DistImportPlanState 对象并初始化 planstate = (DistImportPlanState *)palloc0(sizeof(DistImportPlanState)); + + // 获取外部表的导入选项 GetDistImportOptions(rte->relid, planstate); + + // 获取外部表的位置 location = strVal(lfirst(list_head(planstate->source))); + + // 如果外部表位置不在本地 if (!is_local_location(location)) { uint32 distSessionKey; List *tasklist = NIL; // generate session key distSessionKey = generate_unique_id(>_sessionId); + // 将会话密钥添加到 FDW 私有数据列表中 fdwPriv = lappend(fdwPriv, makeDefElem(pstrdup(optSessionKey), (Node *)makeInteger((long)distSessionKey))); // get task list - tasklist = assignTaskToDataNode(planstate->source, MODE_NORMAL, ((Plan *)plan)->exec_nodes->nodeList, 1, - planstate); + tasklist = + assignTaskToDataNode(planstate->source, MODE_NORMAL, ((Plan *)plan)->exec_nodes->nodeList, 1, planstate); + // 将任务列表添加到 FDW 私有数据列表中 fdwPriv = lappend(fdwPriv, makeDefElem(pstrdup(optTaskList), (Node *)tasklist)); } - return fdwPriv; + return fdwPriv; // 返回 FDW 私有数据列表 } - +/* + * 功能:初始化分布式导出 + * + * 参数列表: + * mtstate:ModifyTableState 对象,用于修改表的状态 + * rinfo:ResultRelInfo 对象,表示结果关系的信息 + * fdw_private:FDW 私有数据列表 + * subplan_index:子计划的索引 + * eflags:执行标志 + * + * 注意: + * 该函数用于初始化分布式导出操作,根据执行标志和外部表的位置决定如何处理导出。 + * 如果执行标志包括 EXEC_FLAG_EXPLAIN_ONLY,则不执行任何操作。 + * 否则,根据外部表的位置决定是本地导出还是远程导出,并执行相应的操作。 + */ static void distExportBegin(ModifyTableState *mtstate, ResultRelInfo *rinfo, List *fdw_private, int subplan_index, int eflags) { + // 如果执行标志包括 EXEC_FLAG_EXPLAIN_ONLY,则返回,不执行任何操作 if ((uint32)eflags & EXEC_FLAG_EXPLAIN_ONLY) return; - List *options = NIL; - char *location = NULL; - Relation rel = rinfo->ri_RelationDesc; - bool isRemote = false; - uint32 sessionKey = 0; - List *tasklist = NIL; + List *options = NIL; // 外部表的选项列表 + char *location = NULL; // 外部表的位置 + Relation rel = rinfo->ri_RelationDesc; // 结果关系的描述符 + bool isRemote = false; // 是否为远程导出 + uint32 sessionKey = 0; // 会话密钥 + List *tasklist = NIL; // 任务列表 + // 获取外部表的位置和选项 distGetOptions(RelationGetRelid(rel), &location, &options); + // 判断是否为远程导出 isRemote = !is_local_location(location); // Add decrpyt function for obs access key and security access key in obs options decryptOBSForeignTableOption(&options); + // 默认的输出格式后缀为 strTextFormat const char *suffix = strTextFormat; + // 检查外部表选项,获取输出格式后缀和格式化器选项 ListCell *lc = NULL; foreach (lc, options) { DefElem *defel = (DefElem *)lfirst(lc); @@ -1741,13 +2063,16 @@ static void distExportBegin(ModifyTableState *mtstate, ResultRelInfo *rinfo, Lis UntransformFormatterOption(defel); } + // 遍历 FDW 私有数据列表,获取会话密钥和任务列表 foreach (lc, fdw_private) { DefElem *def = (DefElem *)lfirst(lc); if (strcasecmp(def->defname, optSessionKey) == 0) { if (IS_PGXC_COORDINATOR) { + // 如果是协调节点,生成会话密钥并设置到 FDW 私有数据中 sessionKey = generate_unique_id(>_sessionId); def->arg = (Node *)makeInteger((long)sessionKey); } else { + // 如果是数据节点,从 FDW 私有数据中获取会话密钥 sessionKey = (uint32)intVal(def->arg); } ereport(DEBUG1, (errcode(ERRCODE_DEBUG), errmsg("Session id: %u", sessionKey))); @@ -1756,32 +2081,41 @@ static void distExportBegin(ModifyTableState *mtstate, ResultRelInfo *rinfo, Lis tasklist = (List *)def->arg; } + // 反序列化外部表的位置,生成位置列表 List *locations = DeserializeLocations(location); Assert(list_length(locations) >= 1); // coordinator doesn't create any directory or file, and has no file handle. + // 如果是流式计划或远程导出,执行远程导出操作 if (IS_STREAM_PLAN || isRemote) { char *filename = isRemote ? location : strVal(lfirst(list_head(locations))); + // 初始化外部导出,并将结果存储到结果关系的 FDW 状态中 rinfo->ri_FdwState = (void *)beginExport(rel, filename, options, isRemote, sessionKey, tasklist); } else if (!isRemote) { const char *userExportDir = strVal(lfirst(list_head(locations))); + // 如果外部导出目录以 'file://' 前缀开头,则去除前缀 if (strncmp(userExportDir, LOCAL_PREFIX, LOCAL_PREFIX_LEN) == 0) { // remove the prefix string 'file://' userExportDir = userExportDir + LOCAL_PREFIX_LEN; } + // 初始化本地导出环境 InitExportEnvirnment(userExportDir); // outfile isn't allocated in CopyState memory context, so free it by calling pfree(); // later, when switching to new segment file, do the same thing again; char *relname = get_rel_name(RelationGetRelid(rel)); char *outfile = distExportNextFileName(t_thrd.bulk_cxt.distExportDataDir, relname, suffix); + // 初始化外部导出,并将结果存储到结果关系的 FDW 状态中 rinfo->ri_FdwState = (void *)beginExport(rel, outfile, options, false, sessionKey, tasklist); + // 处理导出文件头部 ProcessFileHeader((CopyState)rinfo->ri_FdwState); + // 释放文件名内存 pfree(outfile); } #ifndef ENABLE_MULTIPLE_NODES + // 验证是否支持 GDS (Gauss Data Service) CopyState cstate_for_verify = (CopyState)rinfo->ri_FdwState; if (cstate_for_verify->remoteExport && cstate_for_verify->copy_dest == COPY_GDS) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Un-supported feature"), @@ -1789,7 +2123,22 @@ static void distExportBegin(ModifyTableState *mtstate, ResultRelInfo *rinfo, Lis } #endif } - +/* + * 功能:执行分布式导出操作 + * + * 参数列表: + * estate:EState 对象,用于执行表达式和计划 + * resultRelInfo:ResultRelInfo 对象,表示结果关系的信息 + * slot:TupleTableSlot 对象,表示元组数据的槽 + * planSlot:TupleTableSlot 对象,表示计划数据的槽 + * + * 注意: + * 该函数执行分布式导出操作,将元组数据写入外部导出文件。 + * 首先检查当前导出文件的大小是否超过指定的最大分段大小(distExportMaxSegSize)。 + * 如果超过,则切换到新的分段文件。 + * 然后调用 execExport 函数将元组数据写入导出文件。 + * 最后返回元组数据槽。 + */ static TupleTableSlot *distExportExec(EState *estate, ResultRelInfo *resultRelInfo, TupleTableSlot *slot, TupleTableSlot *planSlot) { @@ -1804,27 +2153,55 @@ static TupleTableSlot *distExportExec(EState *estate, ResultRelInfo *resultRelIn exportResetTotalSize(cstate); } + // 执行导出操作,将元组数据写入导出文件 execExport(cstate, slot); + // 返回元组数据槽 return slot; } +/* + * 功能:结束分布式导出操作 + * + * 参数列表: + * estate:EState 对象,用于执行表达式和计划 + * resultRelInfo:ResultRelInfo 对象,表示结果关系的信息 + * + * 注意: + * 该函数用于结束分布式导出操作,释放相关资源。它检查 resultRelInfo 结构中的 ri_FdwState + * 是否为非空,如果是,则调用 endExport 函数结束导出操作并释放资源。 + */ static void distExportEnd(EState *estate, ResultRelInfo *resultRelInfo) { if (resultRelInfo->ri_FdwState != NULL) { + // 调用 endExport 函数结束导出操作并释放资源 endExport((CopyState)resultRelInfo->ri_FdwState); } } - +/* + * 功能:切换到下一个段文件进行导出 + * + * 参数列表: + * cstate:CopyState 对象,表示复制操作的状态 + * rel:Relation 对象,表示正在导出的关系 + * + * 注意: + * 该函数用于切换到下一个段文件,以继续数据导出。它首先获取关系的导出选项,查找导出格式选项, + * 并据此确定下一个段文件的文件名后缀。然后,它调用 exportAllocNewFile 函数来分配新的文件, + * 用于接下来的导出操作。 + */ static void distExportSwitchSegment(CopyState cstate, Relation rel) { List *options = NULL; char *location = NULL; + // 获取关系的导出选项 distGetOptions(RelationGetRelid(rel), &location, &options); const char *suffix = strTextFormat; ListCell *lc = NULL; + // 查找导出格式选项 foreach (lc, options) { DefElem *defel = (DefElem *)lfirst(lc); + // 如果选项的名称以 "FORMAT" 开头,则提取文件名后缀 if (strncasecmp(defel->defname, optFormat, 6) == 0) { suffix = FetchAndCheckFormat(defel); break; @@ -1833,12 +2210,28 @@ static void distExportSwitchSegment(CopyState cstate, Relation rel) // switch to next segment file to copy to char *relname = get_rel_name(RelationGetRelid(rel)); + // 生成下一个段文件的文件名 char *outfile = distExportNextFileName(t_thrd.bulk_cxt.distExportDataDir, relname, suffix); + // 分配新的文件,用于接下来的导出操作 exportAllocNewFile(cstate, outfile); + // 释放文件名内存 pfree(outfile); } // File Name: table name + txid + datanode id + segment no + suffix +/* + * 功能:生成下一个段文件的文件名 + * + * 参数列表: + * abspath:表示绝对路径的字符串 + * relname:表示关系名称的字符串 + * suffix:表示文件名后缀的字符串 + * + * 注意: + * 该函数用于生成下一个段文件的文件名,以用于数据导出操作。在同一导出事务中,段编号(segno) + * 是生成文件名的唯一变量。 (abspath, relname, distExportCurrXid) 可用于标识唯一的导出操作。 + * (distExportTimestampStr, segno) 可用于避免相同文件名的冲突。 + */ static char *distExportNextFileName(const char *abspath, const char *relname, const char *suffix) { // in the same export transaction, segment no is the only var for filenmae. @@ -1855,44 +2248,90 @@ static char *distExportNextFileName(const char *abspath, const char *relname, co return pstrdup(temp); } +/* + * 功能:获取并检查导出格式选项 + * + * 参数列表: + * defel:指向导出格式选项的 DefElem 结构 + * + * 注意: + * 该函数用于获取并检查导出格式选项,确保它的值是有效的导出格式之一("csv" 或 "text")。 + * 如果格式无效,将生成一个错误报告。如果格式有效,则返回格式字符串。 + */ static const char *FetchAndCheckFormat(DefElem *defel) { + // 获取导出格式选项的字符串值 char *format = defGetString(defel); + + // 检查格式是否为 "csv"(不区分大小写) if (0 == strncasecmp(format, strCsvFormat, 3)) return strCsvFormat; + // 检查格式是否为 "text"(不区分大小写) if (0 == strncasecmp(format, strTextFormat, 4)) return strTextFormat; - if (0 == strncasecmp(format, strFixedFormat, 5)) - return strFixedFormat; - + // 如果格式无效,生成错误报告并抛出异常 ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("only both text && csv formats are supported for foreign table"))); + + // 返回默认的文本格式,这里的返回语句实际上不会被执行 return strTextFormat; /* make complier mute */ } +/* + * 功能:生成时间戳字符串 + * + * 注意: + * 该函数用于生成一个时间戳字符串,表示当前日期和时间。它通过获取当前时间(time_t 类型) + * 并将其格式化为指定格式的字符串,然后将结果存储在全局变量 t_thrd.bulk_cxt.distExportTimestampStr 中。 + * 如果生成的字符串长度不等于 14,将生成一个错误报告。 + */ static void getTimestampStr(void) { + // 用于存储当前时间的 time_t 变量 time_t currTime; + + // 用于存储时间结构的结果 struct tm result; + + // 获取当前时间 currTime = time(NULL); + + // 将当前时间转换为本地时间,并存储在 result 结构中 localtime_r(&currTime, &result); + // 使用 strftime 函数将时间格式化为字符串,并返回生成的字符数 size_t num = strftime(t_thrd.bulk_cxt.distExportTimestampStr, 15, "%Y%m%d%H%M%S", &result); + + // 检查生成的字符串长度是否为 14 if (num != 14) { ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("invalid timestamp string length"))); } + // 在生成的字符串末尾添加 null 终止符 t_thrd.bulk_cxt.distExportTimestampStr[num] = '\0'; } - +/* + * 功能:检查并获取用户导出目录 + * + * 参数列表: + * userExportDir:用户指定的导出目录的绝对路径 + * + * 注意: + * 该函数用于检查用户指定的导出目录是否存在,并将其存储在全局变量 t_thrd.bulk_cxt.distExportDataDir 中。 + * 用户导出目录必须已经存在,并且是一个目录而不是文件。函数还确保 distExportDataDir 以目录分隔符结尾, + * 并检查导出目录路径的长度是否超过了最大限制。 + */ static void CheckAndGetUserExportDir(const char *userExportDir) { // user must have created data directory, otherwise error reported. // the filepath user defined must be an existing absolute path. + // 用于存储文件状态的结构体 struct stat st; + // 用于处理字符串操作的错误码 errno_t rc = EOK; + // 使用 stat 函数检查用户导出目录是否存在,并处理相关错误 if (stat(userExportDir, &st) != 0 && errno != EEXIST) { ereport(ERROR, (errcode_for_file_access(), errmsg("%s doesn't exist, please create it first", userExportDir))); } else if (!S_ISDIR(st.st_mode)) { @@ -1916,10 +2355,22 @@ static void CheckAndGetUserExportDir(const char *userExportDir) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("location filepath is too long when importing data to foreign table"))); } - +/* + * 功能:如果必要,创建目录 + * + * 参数列表: + * mydir:要创建的目录的绝对路径 + * + * 注意: + * 该函数用于检查指定的目录是否存在,如果不存在则创建。如果目录已经存在但不是目录类型,将报错。 + * 函数首先使用 stat 函数检查目录是否存在,如果存在但不是目录类型,将报错。如果目录不存在, + * 将使用 mkdir 函数创建目录。如果目录已经被其他线程创建,函数会再次检查目录是否存在以避免竞态条件。 + */ static void CreateDirIfNecessary(const char *mydir) { + // 用于存储文件状态的结构体 struct stat st; + // 使用 stat 函数检查目录是否存在 if (0 == stat(mydir, &st)) { if (!S_ISDIR(st.st_mode)) ereport(ERROR, (errcode_for_file_access(), @@ -1933,30 +2384,49 @@ static void CreateDirIfNecessary(const char *mydir) if ((0 != mkdir(t_thrd.bulk_cxt.distExportDataDir, S_IRWXU)) && (errno != EEXIST)) ereport(ERROR, (errcode_for_file_access(), errmsg("could not create directory \"%s\": %m", mydir))); } - +/* + * 功能:初始化导出环境 + * + * 参数列表: + * userExportDir:用户指定的导出目录的绝对路径 + * + * 注意: + * 该函数用于在开始新的事务时重置所有导出环境,以准备执行导出操作。如果当前事务与上一次导出操作的 + * 事务不同,函数将重置环境,包括导出的段文件编号、当前事务编号、时间戳等。函数还会检查和获取 + * 用户指定的导出目录,并创建必要的目录结构,以便存储导出文件。 + */ static void InitExportEnvirnment(const char *userExportDir) { // reset all environments to run exporting when start a new transaction TransactionId curXid = GetCurrentTransactionId(); + // 用于字符串操作的返回码 errno_t rc = EOK; int ret; + // 如果当前事务与上一次导出操作的事务不同,执行环境重置 if (t_thrd.bulk_cxt.distExportCurrXid != curXid) { // segment number alwarys increases by 1 within the same transaction, // even though there are one or more export actions in this transaction. t_thrd.bulk_cxt.distExportNextSegNo = 0; t_thrd.bulk_cxt.distExportCurrXid = curXid; + // 获取当前时间的时间戳字符串,格式为"YYYYMMDDHHMMSS" getTimestampStr(); + // 检查并获取用户指定的导出目录 CheckAndGetUserExportDir(userExportDir); + // 获取当前导出目录的长度 int lenOfDirName = strlen(t_thrd.bulk_cxt.distExportDataDir); + // 定义顶级目录名称的长度 int topDirLen = 8; char topDir[topDirLen + 1]; + // 从时间戳字符串中获取前 8 个字符作为顶级目录名称 rc = strncpy_s(topDir, topDirLen + 1, t_thrd.bulk_cxt.distExportTimestampStr, topDirLen); securec_check(rc, "\0", "\0"); topDir[topDirLen] = '\0'; + // 获取当前节点名称的长度 int lenOfNodeName = (int)strlen(g_instance.attr.attr_common.PGXCNodeName); + // 如果导出目录的长度加上顶级目录名称长度、节点名称长度和分隔符的长度小于最大路径长度 if (lenOfDirName + topDirLen + lenOfNodeName + 2 < MAX_PATH_LEN) { // set the 1-level directory and create it if necessary. // it's named by local date, excluding time info (hours, minutes or seconds). @@ -1969,6 +2439,7 @@ static void InitExportEnvirnment(const char *userExportDir) // set the 2-level directory and create it if necessary. // it's named by node name. + // 设置二级目录并在必要时创建它,以节点名称命名 ret = strcat_s(t_thrd.bulk_cxt.distExportDataDir, MAX_PATH_LEN, g_instance.attr.attr_common.PGXCNodeName); securec_check(ret, "\0", "\0"); lenOfDirName += lenOfNodeName; @@ -1976,30 +2447,46 @@ static void InitExportEnvirnment(const char *userExportDir) t_thrd.bulk_cxt.distExportDataDir[lenOfDirName] = '\0'; CreateDirIfNecessary(t_thrd.bulk_cxt.distExportDataDir); } else + // 导出目录路径过长,导致超出最大路径长度,报错 ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("location filepath is too long when importing data to foreign table"))); } } - +/* + * 功能:转换外部表的格式器选项 + * + * 参数列表: + * stmt:CreateForeignTableStmt 对象,表示创建外部表的语句 + * + * 注意: + * 该函数用于转换外部表列定义中的格式化选项为一组选项,并将其添加到外部表的选项列表中。格式化选项 + * 通常用于指定列的格式化方式,如列名、位置、固定长度等信息。这些选项可以用于自定义外部表的数据格式 + * 以便与外部数据源兼容。 + */ static void TransformFormatterOptions(CreateForeignTableStmt *stmt) { ListCell *lc = NULL; - StringInfo str = makeStringInfo(); - bool hasFormatter = false; + StringInfo str = makeStringInfo(); // 用于构建格式化选项字符串 + bool hasFormatter = false; // 标记是否存在格式化选项 + // 遍历外部表列定义列表 foreach (lc, stmt->base.tableElts) { ColumnDef *coldef = (ColumnDef *)lfirst(lc); + // 如果列定义中包含位置信息 if (coldef->position) { Position *pos = coldef->position; + // 如果不是第一个格式化选项,添加分隔符 '.' if (lc != list_head(stmt->base.tableElts)) appendStringInfoChar(str, '.'); + // 构建格式化选项字符串,包括列名、位置和固定长度 appendStringInfo(str, "%s(%d,%d)", pos->colname, pos->position, pos->fixedlen); hasFormatter = true; } } + // 如果存在格式化选项,将其添加到外部表的选项列表中 if (hasFormatter) stmt->options = lappend(stmt->options, makeDefElem(pstrdup(optFormatter), (Node *)makeString(str->data))); } @@ -2008,23 +2495,42 @@ static void TransformFormatterOptions(CreateForeignTableStmt *stmt) * brief: Validate table definition * input param @obj: A Obj including infomation to validate when alter tabel and create table. */ -static void distValidateAlterTableStmt(Node* Obj) +/* + * 功能:验证 ALTER TABLE 语句是否合法 + * + * 参数列表: + * Obj:Node 对象,表示 ALTER TABLE 语句 + * + * 注意: + * 该函数用于验证 ALTER TABLE 语句是否合法。它首先获取目标表的 OID,并检查目标表是否为 OBS 外部表。 + * 如果目标表是 OBS 外部表,则只允许执行特定类型的 ALTER TABLE 子命令,否则允许执行其他类型的 ALTER TABLE 子命令。 + * 此外,函数还检查是否存在不支持的选项,并禁止设置、添加或删除 error_table 和 write_only 选项。 + */ +static void distValidateAlterTableStmt(Node *Obj) { - ListCell* lc = NULL; - AlterTableStmt* stmt = (AlterTableStmt*)Obj; + ListCell *lc = NULL; + AlterTableStmt *stmt = (AlterTableStmt *)Obj; + // 获取目标表的 OID Oid relId = RangeVarGetRelid(stmt->relation, NoLock, true); + // 检查目标表是否为 OBS 外部表 bool obsTbl = IS_OBS_CSV_TXT_FOREIGN_TABLE(relId); + // 遍历 ALTER TABLE 子命令列表 foreach (lc, stmt->cmds) { - AlterTableCmd* cmd = (AlterTableCmd*)lfirst(lc); + AlterTableCmd *cmd = (AlterTableCmd *)lfirst(lc); + + // 如果目标表是 OBS 外部表 if (obsTbl) { + // 只允许执行特定类型的 ALTER TABLE 子命令 if (!DIST_OBS_SUPPORT_AT_CMD(cmd->subtype)) { ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("Un-support feature"), errdetail("target table is a foreign table"))); } } else { + // 目标表不是 OBS 外部表,允许执行其他类型的 ALTER TABLE 子命令 if (!FOREIGNTABLE_SUPPORT_AT_CMD(cmd->subtype)) { + // 但禁止执行不支持的 ALTER TABLE 子命令,除非是添加索引或删除约束 if (!(AT_AddIndex == cmd->subtype || AT_DropConstraint == cmd->subtype)) { ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("Un-support feature"), errdetail("target table is a foreign table"))); @@ -2034,10 +2540,11 @@ static void distValidateAlterTableStmt(Node* Obj) /* error_table, write_only can not SET, ADD or DROP by ALTER FOREIGN TABLE OPTIONS */ if (cmd->subtype == AT_GenericOptions) { - List* defList = (List*)cmd->def; - ListCell* deflc = NULL; + List *defList = (List *)cmd->def; + ListCell *deflc = NULL; foreach (deflc, defList) { - DefElem* def = (DefElem*)lfirst(deflc); + DefElem *def = (DefElem *)lfirst(deflc); + // 禁止设置、添加或删除 error_table 和 write_only 选项 if (strcmp(def->defname, optErrorRel) == 0 || strcmp(def->defname, optWriteOnly) == 0) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Invalid option %s", def->defname))); } @@ -2045,84 +2552,117 @@ static void distValidateAlterTableStmt(Node* Obj) } } } - -static void distValidateCreateForeignTableStmt(Node* Obj) +/* + * 功能:验证 CREATE FOREIGN TABLE 语句是否合法 + * + * 参数列表: + * Obj:Node 对象,表示 CREATE FOREIGN TABLE 语句 + * + * 注意: + * 该函数用于验证 CREATE FOREIGN TABLE 语句是否合法。它首先检查分布类型是否为 ROUNDROBIN, + * 如果不是,则报错提示只支持 ROUNDROBIN 分布类型。然后,函数验证指定的选项是否有效, + * 并提供有效选项的提示。接着,函数检查是否指定了 error_relation,如果是则添加到扩展选项列表中。 + * 如果指定了 write_only 标志,则将其添加到扩展选项列表中。最后,函数检查是否创建了分区表,如果是则报错。 + * 同时,函数还会调用 TransformFormatterOptions 函数来处理格式化选项。 + */ +static void distValidateCreateForeignTableStmt(Node *Obj) { - CreateForeignTableStmt* stmt = (CreateForeignTableStmt*)Obj; - ListCell* lc = NULL; - List* options_list = stmt->options; - Node* errLog = stmt->error_relation; + CreateForeignTableStmt *stmt = (CreateForeignTableStmt *)Obj; + ListCell *lc = NULL; + List *options_list = stmt->options; + Node *errLog = stmt->error_relation; Oid catalog = ForeignTableRelationId; - DistributeBy* DisByOp = ((CreateStmt*)Obj)->distributeby; + DistributeBy *DisByOp = ((CreateStmt *)Obj)->distributeby; + // 检查分布类型是否为 ROUNDROBIN if (NULL != DisByOp && DISTTYPE_ROUNDROBIN != DisByOp->disttype) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Unsupport distribute type."), errdetail("Supported option value is \"roundrobin\"."))); } + // 遍历选项列表,检查选项是否有效 foreach (lc, options_list) { - DefElem* def = (DefElem*)lfirst(lc); + DefElem *def = (DefElem *)lfirst(lc); + // 检查选项是否有效,否则报错 if (!is_valid_option(def->defname, catalog)) { - const struct DistFdwOption* opt = NULL; + const struct DistFdwOption *opt = NULL; StringInfoData buf; /* * Unknown option specified, complain about it. Provide a hint * with list of valid options for the object. */ + // 未知选项,提供有效选项的提示 initStringInfo(&buf); for (opt = loader_valid_options; opt->optname; opt++) { if (catalog == opt->optcontext) appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", opt->optname); } - ereport(ERROR, - (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), errmsg("invalid option \"%s\"", def->defname), - buf.len > 0 ? errhint("Valid options in this context are: %s", buf.data) - : errhint("There are no valid options in this context."))); + ereport(ERROR, (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), errmsg("invalid option \"%s\"", def->defname), + buf.len > 0 ? errhint("Valid options in this context are: %s", buf.data) + : errhint("There are no valid options in this context."))); } } + // 处理 error_relation,将其添加到扩展选项列表中 if (errLog != NULL) { if (IsA(errLog, DefElem)) stmt->extOptions = lappend(stmt->extOptions, errLog); else { RangeVar *rv = (RangeVar *)errLog; - stmt->extOptions = lappend(stmt->extOptions, makeDefElem(pstrdup(optErrorRel), - (Node *)makeString(pstrdup(rv->relname)))); + stmt->extOptions = + lappend(stmt->extOptions, makeDefElem(pstrdup(optErrorRel), (Node *)makeString(pstrdup(rv->relname)))); } } + // 如果设置了 write_only 标志,将其添加到扩展选项列表中 if (stmt->write_only) { - DefElem *writeOpt = makeDefElem(pstrdup(optWriteOnly), - (Node *)makeString(pstrdup(stmt->write_only ? "true" : "false"))); + DefElem *writeOpt = + makeDefElem(pstrdup(optWriteOnly), (Node *)makeString(pstrdup(stmt->write_only ? "true" : "false"))); stmt->extOptions = lappend(stmt->extOptions, writeOpt); } + // 检查是否创建了分区表,如果是则报错 if (stmt->part_state != NULL) { - ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), - errmsg("It is not allowed to create partition on this foreign table."))); + ereport(ERROR, + (errcode(ERRCODE_FDW_ERROR), errmsg("It is not allowed to create partition on this foreign table."))); } + // 处理格式化选项 TransformFormatterOptions(stmt); stmt->options = list_concat(stmt->options, stmt->extOptions); } -static void distValidateTableDef(Node* Obj) +/* + * 功能:验证表定义相关的节点是否合法 + * + * 参数列表: + * Obj:Node 对象,表示待验证的表定义相关节点 + * + * 注意: + * 该函数用于验证表定义相关的节点是否合法。它接受一个 Node 对象作为参数,根据节点的类型 + * 调用相应的验证函数进行验证。支持的节点类型包括 ALTER TABLE 语句和 CREATE FOREIGN TABLE 语句。 + * 对于其他未知的节点类型,函数将报错。 + */ +static void distValidateTableDef(Node *Obj) { if (Obj == NULL) return; switch (nodeTag(Obj)) { case T_AlterTableStmt: { + // 对 ALTER TABLE 语句进行验证 distValidateAlterTableStmt(Obj); break; } case T_CreateForeignTableStmt: { + // 对 CREATE FOREIGN TABLE 语句进行验证 distValidateCreateForeignTableStmt(Obj); break; } default: + // 未知的节点类型,报错 ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("unrecognized node type: %d", (int)nodeTag(Obj)))); break; } @@ -2130,23 +2670,44 @@ static void distValidateTableDef(Node* Obj) extern char *TrimStr(const char *str); +/* + * 功能:检查给定的 URL 是否使用 OBS 协议 + * + * 参数列表: + * locations:const char 指针,表示待检查的 URL 字符串 + * + * 返回值: + * 如果 URL 使用 OBS 协议,返回 true;否则返回 false。 + * + * 注意: + * 该函数用于检查给定的 URL 字符串是否使用 OBS (Object Storage Service) 协议或 GSOBS 协议。 + * 它首先对 URL 进行修剪,然后检查修剪后的字符串是否以 OBS 协议或 GSOBS 协议开头,如果是,则返回 true, + * 否则返回 false。如果传递的 URL 为 NULL 或者修剪后为空字符串,函数将报错并抛出异常。 + */ bool is_obs_protocol(const char *locations) { bool result = false; + // 断言输入 URL 非空 Assert(locations != NULL); - char *trimed_locations = TrimStr(locations); - if (trimed_locations == NULL) { - ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("Invalid URL \"%s\" in trimed LOCATION", trimed_locations))); + // 修剪 URL 字符串 + char *trimmed_locations = TrimStr(locations); + + // 检查修剪后的 URL 是否为空 + if (trimmed_locations == NULL) { + ereport(ERROR, + (errcode(ERRCODE_FDW_ERROR), errmsg("Invalid URL \"%s\" in trimmed LOCATION", trimmed_locations))); } - if (pg_strncasecmp(trimed_locations, GSOBS_PREFIX, GSOBS_PREFIX_LEN) == 0 || - pg_strncasecmp(trimed_locations, OBS_PREFIX, OBS_PREfIX_LEN) == 0) { + // 检查 URL 是否以 OBS 协议或 GSOBS 协议开头 + if (pg_strncasecmp(trimmed_locations, GSOBS_PREFIX, GSOBS_PREFIX_LEN) == 0 || + pg_strncasecmp(trimmed_locations, OBS_PREFIX, OBS_PREFIX_LEN) == 0) { result = true; } - pfree(trimed_locations); + // 释放修剪后的 URL 内存 + pfree(trimmed_locations); return result; } diff --git a/src/gausskernel/storage/bulkload/foreignroutine.cpp b/src/gausskernel/storage/bulkload/foreignroutine.cpp index d470504e1..355d86d75 100644 --- a/src/gausskernel/storage/bulkload/foreignroutine.cpp +++ b/src/gausskernel/storage/bulkload/foreignroutine.cpp @@ -2136,7 +2136,7 @@ void ReportIllegalCharExceptionThreshold() /* * 功能:执行分布式导入操作,读取外部数据并填充元组表槽 * - * 参数: + * 参数列表: * node:外部扫描状态 * * 返回值: @@ -2251,7 +2251,7 @@ retry: /* * 功能:结束分布式导入操作,释放相关资源 * - * 参数: + * 参数列表: * node:外部扫描状态 * * 注意: diff --git a/src/gausskernel/storage/bulkload/parser.cpp b/src/gausskernel/storage/bulkload/parser.cpp index e716eb5e1..d2cc2f803 100644 --- a/src/gausskernel/storage/bulkload/parser.cpp +++ b/src/gausskernel/storage/bulkload/parser.cpp @@ -78,7 +78,7 @@ using namespace GDS; #ifdef GDS_SERVER extern gds_settings settings; -extern THR_LOCAL GDS_Connection* current_connection; +extern THR_LOCAL GDS_Connection *current_connection; /* GDS server required declearations */ #define parser_log gs_ereport @@ -86,10 +86,10 @@ extern THR_LOCAL GDS_Connection* current_connection; #define parser_securec_check_ss gds_securec_check_ss /* extern from gds_main.cpp */ -extern string UriToLocalPath(const char* strUri); +extern string UriToLocalPath(const char *strUri); extern size_t GetDataSegmentSize(); -extern char* gs_strerror(int errnum); -static void GetFileHeader(WritableParser* self, const char* path); +extern char *gs_strerror(int errnum); +static void GetFileHeader(WritableParser *self, const char *path); #endif #ifdef OBS_SERVER @@ -99,45 +99,70 @@ static void GetFileHeader(WritableParser* self, const char* path); #define parser_securec_check_ss(rc) securec_check_ss(rc, "\0", "\0") #ifndef ENABLE_LITE_MODE -static size_t SourceRead_OBS(Source* self, void* buffer, size_t len); -static bool SourceNext_OBS(Source* self); +static size_t SourceRead_OBS(Source *self, void *buffer, size_t len); +static bool SourceNext_OBS(Source *self); #endif #endif -static Source* CreateSource(const FileList* files, SourceType sourcetype); -static void DestroyParser(Parser* self); -static void DestroyReadableParser(ReadableParser* self); -static void DestroyWritableParser(WritableParser* self); -static void CleanupWritablParser(WritableParser* self); -static void NopCleanup(Parser* self); +static Source *CreateSource(const FileList *files, SourceType sourcetype); +static void DestroyParser(Parser *self); +static void DestroyReadableParser(ReadableParser *self); +static void DestroyWritableParser(WritableParser *self); +static void CleanupWritablParser(WritableParser *self); +static void NopCleanup(Parser *self); -static char* FindEolChar(char* s, size_t len, const char* eol, int* eol_cur, int* eol_cur_saved) +/* + * 功能:查找字符串中的行尾字符 + * + * 参数列表: + * s:char 指针,表示待查找的字符串 + * len:size_t,表示字符串的长度 + * eol:const char 指针,表示行尾字符的定义 + * eol_cur:int 指针,用于保存当前行尾字符的位置 + * eol_cur_saved:int 指针,用于保存之前的行尾字符的位置 + * + * 返回值: + * 如果找到行尾字符,返回指向该字符的指针;否则返回 NULL。 + * + * 注意: + * 该函数用于在字符串中查找行尾字符,行尾字符可以是 '\r'、'\n' 或自定义的字符串。 + * 当 eol 参数为 NULL 时,函数会查找 '\r' 或 '\n',并返回第一个找到的字符。 + * 当 eol 参数不为 NULL 时,函数会查找自定义的行尾字符串,并返回整个行尾字符串的起始位置。 + */ + +static char *FindEolChar(char *s, size_t len, const char *eol, int *eol_cur, int *eol_cur_saved) { - char* end = NULL; - char* cr = NULL; - char* lf = NULL; + char *end = NULL; + char *cr = NULL; + char *lf = NULL; end = s + len; + if (eol == NULL) { + // 如果 eol 为 NULL,查找 '\r' 或 '\n' while (s < end) { size_t chunk = (s + CHUNK_SZ < end) ? CHUNK_SZ : (end - s); - cr = (char*)memchr(s, '\r', chunk); - lf = (char*)memchr(s, '\n', chunk); + cr = (char *)memchr(s, '\r', chunk); + lf = (char *)memchr(s, '\n', chunk); + if (cr != NULL) { if (lf != NULL && lf < cr) - return lf; - return cr; + return lf; // 返回找到的 '\n' + return cr; // 返回找到的 '\r' } else if (lf != NULL) - return lf; + return lf; // 返回找到的 '\n' + s += CHUNK_SZ; } } else { int eol_len = strlen(eol); *eol_cur_saved = *eol_cur; + + // 查找自定义的行尾字符串 for (int i = 0; i < (int)len; i++) { if (s[i] == eol[*eol_cur]) { (*eol_cur)++; if (*eol_cur >= eol_len) { - return s + i + 1 + *eol_cur_saved - eol_len; + return s + i + 1 + *eol_cur_saved - eol_len; // 返回整个行尾字符串的起始位置 } continue; } else if (*eol_cur >= 1) { @@ -145,192 +170,333 @@ static char* FindEolChar(char* s, size_t len, const char* eol, int* eol_cur, int // Here we allow i back into -1, and on the beginning of the next loop it will be back to 0, as // expected. i--; + // 保证每个字符都与 eol 进行比较 + // 允许 i 回退到 -1,在下一次循环开始时将其重置为 0 + i--; } *eol_cur = 0; *eol_cur_saved = 0; } } - return NULL; + return NULL; // 未找到行尾字符 } -static Source* CreateSource(const FileList* files, SourceType sourcetype) +/* + * 功能:创建源对象 + * + * 参数列表: + * files:const FileList 指针,表示文件列表 + * sourcetype:SourceType,表示源类型 + * + * 返回值: + * 返回指向创建的 Source 对象的指针。 + * + * 注意: + * 该函数用于创建一个 Source 对象,初始化其属性,并返回指向该对象的指针。 + * 如果内存分配失败,会记录错误日志并返回 NULL。 + */ + +static Source *CreateSource(const FileList *files, SourceType sourcetype) { - Source* self = NULL; + Source *self = NULL; // 声明一个 Source 指针并初始化为 NULL try { - self = new Source; - } catch (std::bad_alloc&) { + self = new Source; // 尝试分配内存并创建 Source 对象 + } catch (std::bad_alloc &) { + // 捕获内存分配异常,记录错误日志并返回 NULL parser_log(LEVEL_ERROR, "failed to create source, out of memory"); } if (files != NULL) - self->SetFileList(*files); - self->SetSourceType(sourcetype); - self->SourceInit(files == NULL); + self->SetFileList(*files); // 如果传入的文件列表不为空,设置 Source 对象的文件列表 - return self; + self->SetSourceType(sourcetype); // 设置 Source 对象的源类型 + self->SourceInit(files == NULL); // 调用 SourceInit 函数,初始化 Source 对象的其他属性 + + return self; // 返回创建的 Source 对象的指针 } /* specified for general file source reading */ -static size_t SourceRead_File(Source* self, void* buffer, size_t len) +/* + * 功能:从文件源中读取数据 + * + * 参数列表: + * self:Source 指针,表示源对象 + * buffer:void 指针,表示数据缓冲区 + * len:size_t,表示要读取的数据长度 + * + * 返回值: + * 返回实际读取的数据长度。 + * + * 注意: + * 该函数用于从文件源中读取数据。它首先尝试执行 POSIX_FADVISE 操作,然后使用 fread 函数从文件中读取数据。 + * 如果在读取数据时发生错误,会记录错误日志。 + */ + +static size_t SourceRead_File(Source *self, void *buffer, size_t len) { - size_t nread; - const char* err_file = self->m_files[self->m_current - 1].c_str(); + size_t nread; // 用于保存实际读取的数据长度 + const char *err_file = self->m_files[self->m_current - 1].c_str(); // 获取当前文件名 #if defined(USE_POSIX_FADVISE) - off_t offset = lseek(fileno(self->m_fd), 0, SEEK_CUR); + off_t offset = lseek(fileno(self->m_fd), 0, SEEK_CUR); // 获取当前文件偏移量 if (offset >= 0) (void)posix_fadvise(fileno(self->m_fd), offset + len, INITIAL_BUF_LEN, POSIX_FADV_WILLNEED); + // 执行 POSIX_FADVISE 操作,提前加载文件数据 #endif - nread = fread(buffer, 1, len, self->m_fd); - if (ferror(self->m_fd)) - parser_log(LEVEL_ERROR, "could not read source file: %s", err_file); - return nread; + nread = fread(buffer, 1, len, self->m_fd); // 使用 fread 函数从文件中读取数据 + if (ferror(self->m_fd)) + parser_log(LEVEL_ERROR, "could not read source file: %s", err_file); // 如果读取出错,记录错误日志 + + return nread; // 返回实际读取的数据长度 } /* specified for pipe source reading */ -static size_t SourceRead_FIFO(Source* self, void* buffer, size_t len) +/* + * 功能:从 FIFO(命名管道)源中读取数据 + * + * 参数列表: + * self:Source 指针,表示源对象 + * buffer:void 指针,表示数据缓冲区 + * len:size_t,表示要读取的数据长度 + * + * 返回值: + * 返回实际读取的数据长度。 + * + * 注意: + * 该函数用于从 FIFO(命名管道)源中读取数据。它使用 read 函数从命名管道中读取数据, + * 如果在读取数据时发生错误,会记录错误日志。 + */ + +static size_t SourceRead_FIFO(Source *self, void *buffer, size_t len) { - const char* err_file = self->m_files[0].c_str(); + const char *err_file = self->m_files[0].c_str(); // 获取管道文件名 #ifndef WIN32 - ssize_t nread = read(self->m_fifo, buffer, len); + ssize_t nread = read(self->m_fifo, buffer, len); // 使用 read 函数从命名管道中读取数据 #else - int nread = read(self->m_fifo, buffer, len); + int nread = read(self->m_fifo, buffer, len); // 使用 read 函数从命名管道中读取数据 #endif + if (-1 == nread) - parser_log(LEVEL_ERROR, "could not read source file: %s", err_file); - return (size_t)nread; + parser_log(LEVEL_ERROR, "could not read source file: %s", err_file); // 如果读取出错,记录错误日志 + + return (size_t)nread; // 返回实际读取的数据长度 } /* specified for general file source checking next file */ -static bool SourceNext_File(Source* self) +/* + * 功能:切换到下一个文件源 + * + * 参数列表: + * self:Source 指针,表示源对象 + * + * 返回值: + * 如果成功切换到下一个文件源,返回 true;否则返回 false。 + * + * 注意: + * 该函数用于切换到下一个文件源,如果当前文件源已经打开,则关闭它。 + * 然后尝试打开下一个文件源,如果打开失败,会记录警告日志。 + */ + +static bool SourceNext_File(Source *self) { - const char* err_file = NULL; - const char* current_file = NULL; + const char *err_file = NULL; // 用于记录错误文件名 + const char *current_file = NULL; // 用于记录当前文件名 if (self->m_fd != NULL) { - fclose(self->m_fd); + fclose(self->m_fd); // 如果当前文件源已经打开,则关闭它 self->m_fd = NULL; } if ((size_t)(self->m_current) >= self->m_files.size()) - return false; + return false; // 如果已经遍历完所有文件源,返回 false + while (self->m_fd == NULL && (size_t)(self->m_current) < self->m_files.size()) { - current_file = self->m_files[self->m_current++].c_str(); - self->m_fd = fopen(current_file, "r"); + current_file = self->m_files[self->m_current++].c_str(); // 获取下一个文件名 + self->m_fd = fopen(current_file, "r"); // 尝试打开下一个文件源 + if (self->m_fd == NULL) { - err_file = self->m_files[self->m_current - 1].c_str(); - parser_log(LEVEL_WARNING, "Unable to open %s", err_file); + err_file = self->m_files[self->m_current - 1].c_str(); // 获取打开失败的文件名 + parser_log(LEVEL_WARNING, "Unable to open %s", err_file); // 记录警告日志 } } if (self->m_fd != NULL) { - self->m_filename = self->m_files[self->m_current - 1]; + self->m_filename = self->m_files[self->m_current - 1]; // 设置当前文件名属性 #if defined(USE_POSIX_FADVISE) - (void)posix_fadvise( - fileno(self->m_fd), 0, INITIAL_BUF_LEN, POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE | POSIX_FADV_WILLNEED); + (void)posix_fadvise(fileno(self->m_fd), 0, INITIAL_BUF_LEN, + POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE | POSIX_FADV_WILLNEED); + // 执行 POSIX_FADVISE 操作,提前加载文件数据 #endif } - return self->m_fd != NULL; + return self->m_fd != NULL; // 返回是否成功打开下一个文件源 } /* specified for pipe source checking next file */ -static bool SourceNext_FIFO(Source* self) +static bool SourceNext_FIFO(Source *self) { return false; } #ifdef GDS_SERVER -void Source::SourceWrite(evbuffer* buffer, size_t len) +/* + * 功能:将数据写入源的写缓冲区 + * + * 参数列表: + * buffer:evbuffer 指针,表示数据缓冲区 + * len:size_t,表示要写入的数据长度 + * + * 注意: + * 该函数用于将数据写入源的写缓冲区。它会尝试将数据从缓冲区中复制到写缓冲区。 + * 如果复制失败,会记录错误日志。如果发现无效的错误记录,也会记录错误日志。 + */ + +void Source::SourceWrite(evbuffer *buffer, size_t len) { - int nsave; - int result; + int nsave; // 用于保存复制的数据长度 + int result; // 用于保存复制结果 while (len > 0) { if (m_wused == MAX_BUFFER_SIZE) - SourceFlush(); - nsave = Min(len, MAX_BUFFER_SIZE - m_wused); - result = evbuffer_remove(buffer, m_writeBuf + m_wused, nsave); + SourceFlush(); // 如果写缓冲区已满,执行写入操作 + + nsave = Min(len, MAX_BUFFER_SIZE - m_wused); // 计算要复制的数据长度 + result = evbuffer_remove(buffer, m_writeBuf + m_wused, nsave); // 从数据缓冲区复制数据到写缓冲区 + if (result == -1) - parser_log(LEVEL_ERROR, "failed to copy data to write buffer"); + parser_log(LEVEL_ERROR, "failed to copy data to write buffer"); // 如果复制失败,记录错误日志 else if (result == 0) - parser_log(LEVEL_ERROR, "invaild error recored"); - m_woffset += result; - len -= result; - m_wused += result; + parser_log(LEVEL_ERROR, "invalid error record"); // 如果发现无效的错误记录,记录错误日志 + + m_woffset += result; // 更新写偏移量 + len -= result; // 减去已复制的数据长度 + m_wused += result; // 更新写缓冲区已用长度 } } #endif -void Source::SourceWrite(const char* buffer, size_t len) +/* + * 功能:将数据写入源的写缓冲区 + * + * 参数列表: + * buffer:const char 指针,表示数据缓冲区 + * len:size_t,表示要写入的数据长度 + * + * 注意: + * 该函数用于将数据写入源的写缓冲区。它会循环将数据从缓冲区中复制到写缓冲区, + * 直到所有数据都被写入或写缓冲区已满。如果复制失败,会记录错误日志。 + */ + +void Source::SourceWrite(const char *buffer, size_t len) { - int nsave; - errno_t rc; + int nsave; // 用于保存复制的数据长度 + errno_t rc; // 用于保存复制结果 while (len > 0) { if (m_wused == MAX_BUFFER_SIZE) - SourceFlush(); - nsave = Min(len, MAX_BUFFER_SIZE - m_wused); - rc = memcpy_s(m_writeBuf + m_wused, nsave, buffer, nsave); - parser_securec_check(rc); - m_woffset += nsave; - len -= nsave; - m_wused += nsave; + SourceFlush(); // 如果写缓冲区已满,执行写入操作 + nsave = Min(len, MAX_BUFFER_SIZE - m_wused); // 计算要复制的数据长度 + rc = memcpy_s(m_writeBuf + m_wused, nsave, buffer, nsave); // 从数据缓冲区复制数据到写缓冲区 + parser_securec_check(rc); // 检查复制是否成功 + m_woffset += nsave; // 更新写偏移量 + len -= nsave; // 减去已复制的数据长度 + m_wused += nsave; // 更新写缓冲区已用长度 } } -size_t Source::SourceWriteInternal(const void* buffer, size_t len) +/* + * 功能:将数据写入当前文件源 + * + * 参数列表: + * buffer:const void 指针,表示数据缓冲区 + * len:size_t,表示要写入的数据长度 + * + * 返回值: + * 返回实际写入的数据长度。 + * + * 注意: + * 该函数用于将数据写入当前文件源。它首先检查文件描述符是否为空,如果为空,则记录错误日志。 + * 然后使用 fwrite 函数将数据写入文件,如果写入出错,也会记录错误日志。 + */ + +size_t Source::SourceWriteInternal(const void *buffer, size_t len) { - size_t nwrite = 0; - const char* err_file = m_files[m_current - 1].c_str(); + size_t nwrite = 0; // 用于保存实际写入的数据长度 + const char *err_file = m_files[m_current - 1].c_str(); // 获取当前文件名 + if (m_fd == NULL) { - parser_log(LEVEL_ERROR, "could not write file: %s, caused by fd empty.", err_file); + parser_log(LEVEL_ERROR, "could not write file: %s, caused by fd empty.", + err_file); // 如果文件描述符为空,记录错误日志 } - nwrite = fwrite(buffer, 1, len, m_fd); + + nwrite = fwrite(buffer, 1, len, m_fd); // 使用 fwrite 函数将数据写入文件 if (ferror(m_fd)) { parser_log(LEVEL_ERROR, "could not write file %s with Error: %s", err_file, gs_strerror(errno)); + // 如果写入出错,记录错误日志 } - return nwrite; + + return nwrite; // 返回实际写入的数据长度 } -void Source::GenerateNewFile(const char* prefix, const char* suffix) -{ - char path[MAX_PATH_LEN] = {0}; - errno_t rc = EOK; +/* + * 功能:生成新文件并切换为当前文件源 + * + * 参数列表: + * prefix:const char 指针,表示新文件的前缀 + * suffix:const char 指针,表示新文件的后缀 + * + * 注意: + * 该函数用于生成新文件并切换为当前文件源。它首先根据前缀和后缀生成新文件的路径。 + * 然后检查文件是否已存在,如果存在则继续生成下一个文件。 + * 接着关闭当前文件,将新文件路径添加到文件列表中,然后尝试以写模式打开新文件。 + * 如果打开失败,会记录错误日志。然后尝试设置新文件的权限。 + * 最后,重置写偏移量为0,并切换为新文件源。 + */ - ASSERT(prefix != NULL); +void Source::GenerateNewFile(const char *prefix, const char *suffix) +{ + char path[MAX_PATH_LEN] = {0}; // 用于保存新文件的路径 + errno_t rc = EOK; // 用于保存 snprintf_s 函数的返回值 + + ASSERT(prefix != NULL); // 断言前缀不为空 for (int i = 0; i < MAX_SEGMENT_NUM; i++) { if (suffix != NULL) rc = snprintf_s(path, MAX_PATH_LEN, MAX_PATH_LEN - 1, "%s/%s.%s.%d", m_path.c_str(), prefix, suffix, i); else rc = snprintf_s(path, MAX_PATH_LEN, MAX_PATH_LEN - 1, "%s/%s.%d", m_path.c_str(), prefix, i); - parser_securec_check_ss(rc); -#ifdef WIN32 - LinuxPathToWin(path); -#endif - if (access(path, F_OK) != -1) - continue; + parser_securec_check_ss(rc); // 检查 snprintf_s 是否成功 - CloseCurrentFile(); - m_files.push_back(path); +#ifdef WIN32 + LinuxPathToWin(path); // 将路径转换为 Windows 格式 +#endif + + if (access(path, F_OK) != -1) + continue; // 如果文件已存在,继续生成下一个文件 + + CloseCurrentFile(); // 关闭当前文件 + m_files.push_back(path); // 将新文件路径添加到文件列表中 m_current++; - m_fd = fopen(path, "w"); + m_fd = fopen(path, "w"); // 尝试以写模式打开新文件 + if (m_fd == NULL) { - parser_log(LEVEL_ERROR, "failed to create new file %s", path); + parser_log(LEVEL_ERROR, "failed to create new file %s", path); // 如果打开失败,记录错误日志 } #ifndef WIN32 if (fchmod(fileno(m_fd), S_IRUSR | S_IWUSR) != 0) { - parser_log(LEVEL_ERROR, "failed to chmod file %s with Error: %s", path, gs_strerror(errno)); + parser_log(LEVEL_ERROR, "failed to chmod file %s with Error: %s", path, + gs_strerror(errno)); // 尝试设置新文件的权限 } #endif - m_woffset = 0; - return; + + m_woffset = 0; // 重置写偏移量为0 + return; // 切换为新文件源 } parser_log(LEVEL_ERROR, "failed to generate new file, because of too many files in directory"); + // 如果生成新文件失败(文件数过多),记录错误日志 } /** @@ -341,117 +507,180 @@ void Source::GenerateNewFile(const char* prefix, const char* suffix) * @param suffix Export file path suffix. */ #ifdef GDS_SERVER -void Source::GenerateNewFileForExport(const char* prefix, const char* suffix) -{ - char path[MAX_PATH_LEN] = {0}; - errno_t rc = EOK; +/* + * 功能:为导出生成新文件并切换为当前文件源 + * + * 参数列表: + * prefix:const char 指针,表示新文件的前缀 + * suffix:const char 指针,表示新文件的后缀 + * + * 注意: + * 该函数用于为导出生成新文件并切换为当前文件源。它首先根据前缀和后缀生成新文件的路径。 + * 如果前缀中包含无效符号,则记录错误日志。然后检查文件是否已存在,如果存在则继续生成下一个文件。 + * 对于已生成的文件,如果不存在则记录错误日志。 + * 接着删除旧文件(如果存在),关闭当前文件,将新文件路径添加到文件列表中,然后尝试以写模式打开新文件。 + * 如果打开失败,会记录错误日志。然后尝试设置新文件的权限。 + * 最后,重置写偏移量为0,并切换为新文件源。 + */ - ASSERT(prefix != NULL); +void Source::GenerateNewFileForExport(const char *prefix, const char *suffix) +{ + char path[MAX_PATH_LEN] = {0}; // 用于保存新文件的路径 + errno_t rc = EOK; // 用于保存 snprintf_s 函数的返回值 + + ASSERT(prefix != NULL); // 断言前缀不为空 if (strstr(prefix, InvalidSymbol) != NULL) { - parser_log(LEVEL_ERROR, "invalid path which include \"%s\"", InvalidSymbol); + parser_log(LEVEL_ERROR, "invalid path which include \"%s\"", + InvalidSymbol); // 如果前缀包含无效符号,记录错误日志 } + for (int i = 0; i < MAX_SEGMENT_NUM; i++) { if (suffix != NULL) { rc = snprintf_s(path, MAX_PATH_LEN, MAX_PATH_LEN - 1, "%s/%s.%s.%d", m_path.c_str(), prefix, suffix, i); } else { rc = snprintf_s(path, MAX_PATH_LEN, MAX_PATH_LEN - 1, "%s/%s.%d", m_path.c_str(), prefix, i); } - parser_securec_check_ss(rc); + parser_securec_check_ss(rc); // 检查 snprintf_s 是否成功 + #ifdef WIN32 - LinuxPathToWin(path); + LinuxPathToWin(path); // 将路径转换为 Windows 格式 #endif + if (i < m_current) { if (access(path, F_OK) != 0) { parser_log(LEVEL_ERROR, - "export files incomplete because \"%s\" maybe deleted unexpected or no " - "permission to access,please delete rest of invalid export file.", - path); + "export files incomplete because \"%s\" maybe deleted unexpectedly or no " + "permission to access, please delete the rest of the invalid export files.", + path); // 对于已生成的文件,如果不存在,则记录错误日志 } } else { if (access(path, F_OK) == 0) { - unlink(path); + unlink(path); // 删除旧文件(如果存在) if (settings.debug_level >= DEBUG_NORMAL) { - gs_elog( - LEVEL_LOG, "delete export file during segment file generate for cn retry or repeat export."); + gs_elog(LEVEL_LOG, + "delete export file during segment file generation for CN retry or repeat export."); } } - CloseCurrentFile(); - m_files.push_back(path); + + CloseCurrentFile(); // 关闭当前文件 + m_files.push_back(path); // 将新文件路径添加到文件列表中 m_current++; - CanonicalizePath(path); - m_fd = fopen(path, "w"); + CanonicalizePath(path); // 规范化文件路径 + m_fd = fopen(path, "w"); // 尝试以写模式打开新文件 + if (m_fd == NULL) { parser_log(LEVEL_ERROR, - "failed to create new file %s during segment file generate,please delete rest of invalid export " - "file with Error %s.", - path, - strerror(errno)); + "failed to create new file %s during segment file generation, please delete the rest of the " + "invalid export " + "files with Error: %s.", + path, strerror(errno)); // 如果打开失败,记录错误日志 } + #ifndef WIN32 if (fchmod(fileno(m_fd), LOG_PERM_GRPR) != 0) { - parser_log(LEVEL_ERROR,"Could not change permissions of file \"%s\"\n", path); + parser_log(LEVEL_ERROR, "Could not change permissions of file \"%s\"\n", path); // 尝试设置新文件的权限 } #endif - m_woffset = 0; - return; + + m_woffset = 0; // 重置写偏移量为0 + return; // 切换为新文件源 } } - parser_log(LEVEL_ERROR, "failed to generate new file, because of too many files in directory"); + parser_log(LEVEL_ERROR, "failed to generate new file, because there are too many files in the directory"); + // 如果生成新文件失败(文件数过多),记录错误日志 } #endif +/* + * 功能:刷新源的写缓冲区 + * + * 注意: + * 该函数用于刷新源的写缓冲区。它会循环将写缓冲区中的数据写入当前文件源, + * 直到写缓冲区中的数据全部写入或写入出错。 + */ + void Source::SourceFlush() { - char* buf = m_writeBuf; + char *buf = m_writeBuf; // 初始化缓冲区指针为写缓冲区的起始地址 while (m_wused > 0) { - int nwrite = SourceWriteInternal(buf, m_wused); - buf += nwrite; - m_wused -= nwrite; + int nwrite = SourceWriteInternal(buf, m_wused); // 调用 SourceWriteInternal 函数将数据写入当前文件源 + buf += nwrite; // 更新缓冲区指针 + m_wused -= nwrite; // 更新写缓冲区已用长度 } } +/* + * 功能:关闭当前文件源 + * + * 注意: + * 该函数用于关闭当前文件源。首先会调用 SourceFlush 函数刷新写缓冲区, + * 然后关闭文件描述符,并将文件名属性清空。 + */ + void Source::CloseCurrentFile() { - SourceFlush(); + SourceFlush(); // 刷新写缓冲区 if (m_fd != NULL) { - fclose(m_fd); + fclose(m_fd); // 关闭文件描述符 } - m_fd = NULL; + m_fd = NULL; // 将文件描述符置为空 - m_filename = ""; + m_filename = ""; // 清空文件名属性 } +/* + * 功能:关闭当前文件源(不刷新写缓冲区) + * + * 注意: + * 该函数用于关闭当前文件源,但不会刷新写缓冲区。它会关闭文件描述符, + * 并将文件名属性清空,同时将写缓冲区已用长度置为0。 + */ + void Source::CloseCurrentFileNoFlush() { if (m_fd != NULL) { - fclose(m_fd); + fclose(m_fd); // 关闭文件描述符 } - m_fd = NULL; - m_wused = 0; - m_filename = ""; + m_fd = NULL; // 将文件描述符置为空 + m_wused = 0; // 将写缓冲区已用长度置为0 + m_filename = ""; // 清空文件名属性 } +/* + * 功能:初始化源对象 + * + * 参数列表: + * isWrite:bool,表示是否是写操作 + * + * 注意: + * 该函数用于初始化源对象。如果是写操作,会分配写缓冲区内存。 + * 如果是读操作,会根据不同的情况选择源的读取和下一个源的函数。 + * 如果是从管道中读取数据,则会将读取和下一个源的函数设置为读取管道的函数。 + * 如果是从文件中读取数据,则会将读取和下一个源的函数设置为从文件中读取的函数。 + * 最后,如果没有可以打开的文件,会记录错误日志。 + */ + void Source::SourceInit(bool isWrite) { if (isWrite) { - m_writeBuf = (char*)malloc(MAX_BUFFER_SIZE); + m_writeBuf = (char *)malloc(MAX_BUFFER_SIZE); // 分配写缓冲区内存 if (m_writeBuf == NULL) - parser_log(LEVEL_ERROR, "failed to init source, out of memory"); + parser_log(LEVEL_ERROR, "failed to init source, out of memory"); // 如果分配内存失败,记录错误日志 } else { #ifndef WIN32 if (1 == m_files.size() && m_sourcetype == SOURCE_TYPE_FILE) { struct stat status; - const char* pipe_file = m_files[0].c_str(); + const char *pipe_file = m_files[0].c_str(); if (stat(pipe_file, &status)) parser_log(LEVEL_ERROR, "failed to init source"); /* detect fifo */ if (S_ISFIFO(status.st_mode)) { if (-1 == (m_fifo = open(pipe_file, O_RDONLY))) parser_log(LEVEL_ERROR, "failed to init source"); - SourceRead = SourceRead_FIFO; - SourceNext = SourceNext_FIFO; + SourceRead = SourceRead_FIFO; // 设置读取函数为从管道中读取数据的函数 + SourceNext = SourceNext_FIFO; // 设置下一个源函数为从管道中读取下一个源的函数 return; } } @@ -479,14 +708,24 @@ void Source::SourceInit(bool isWrite) #endif if (!SourceNext(this)) - parser_log(LEVEL_ERROR, "No files can be opened"); + parser_log(LEVEL_ERROR, "No files can be opened"); // 如果没有可以打开的文件,记录错误日志 } } #ifdef WIN32 -void LinuxPathToWin(char* path) +/* + * 功能:将 Linux 路径转换为 Windows 路径 + * + * 参数列表: + * path:char 指针,表示待转换的路径字符串 + * + * 注意: + * 该函数用于将 Linux 路径转换为 Windows 路径。它会将路径中的所有斜杠'/'替换为反斜杠'\'。 + */ + +void LinuxPathToWin(char *path) { - char* slash = NULL; + char *slash = NULL; if (path == NULL) return; while (1) { @@ -494,17 +733,40 @@ void LinuxPathToWin(char* path) if (slash == NULL) break; - *slash = '\\'; + *slash = '\\'; // 将斜杠替换为反斜杠 } } #endif -static void NopReadLine(Parser* self, LineBuffer& buf) +/* + * 功能:空操作读取行函数 + * + * 参数列表: + * self:Parser 指针,表示解析器对象 + * buf:LineBuffer 引用,表示读取行的缓冲区 + * + * 注意: + * 该函数用于表示解析器不支持读取操作,仅用于记录错误日志。 + */ + +static void NopReadLine(Parser *self, LineBuffer &buf) { parser_log(LEVEL_ERROR, "parser doesn't support read"); } -static void NopWriteLine(Parser* self, struct evbuffer& buf, size_t len) +/* + * 功能:无操作写入行 + * + * 参数列表: + * self:Parser 指针,表示解析器对象 + * buf:evbuffer 引用,表示待写入的数据缓冲区 + * len:size_t,表示待写入的数据长度 + * + * 注意: + * 该函数用于表示解析器不支持写入操作,因此会记录错误日志。 + */ + +static void NopWriteLine(Parser *self, struct evbuffer &buf, size_t len) { parser_log(LEVEL_ERROR, "parser doesn't support write"); } @@ -523,21 +785,21 @@ static void NopWriteLine(Parser* self, struct evbuffer& buf, size_t len) * (2) If the linebuffer is full a parsed complete line MUST BE put in linebuffer BEFORE next line parsing. */ template -inline static ParserResult CSVReadLine(CSVParser* self, LineBuffer& buf) +inline static ParserResult CSVReadLine(CSVParser *self, LineBuffer &buf) { bool need_data = false; - char* raw_buffer = self->rec_buf; + char *raw_buffer = self->rec_buf; char quotec = self->quote; char escapec = self->escape; - Source* source = self->source; + Source *source = self->source; /* * All the following flags MUST BE synchronized to appropriate state before * each parsed and completed line is to be added to LineBuffer. */ - bool* in_quote = &(self->in_quote); - bool* last_was_esc = &(self->lastWasEsc); - bool* in_cr = &(self->in_cr); + bool *in_quote = &(self->in_quote); + bool *last_was_esc = &(self->lastWasEsc); + bool *in_cr = &(self->in_cr); char c; int begin_index = self->cur; @@ -695,19 +957,35 @@ inline static ParserResult CSVReadLine(CSVParser* self, LineBuffer& buf) self->cur_need_flush = raw_buf_ptr; return RESULT_SUCCESS; } - +/* + * 功能:文本读取行 + * + * 参数列表: + * self:ReadableParser 指针,表示可读解析器对象 + * buf:LineBuffer 引用,表示用于存储读取的行数据的缓冲区 + * + * 返回值: + * 返回 ParserResult 枚举值,表示读取行的结果,可能为 RESULT_EOF、RESULT_BUFFER_FULL 或 RESULT_SUCCESS。 + * + * 注意: + * 该函数用于文本读取行操作。根据参数 skipData 的值,可以选择是否跳过数据。 + * 函数会循环读取数据并查找行尾字符,直到找到行尾字符或遇到文件结尾。 + * 如果找到行尾字符,则将行数据存储到缓冲区 buf 中,并返回 RESULT_SUCCESS。 + * 如果缓冲区不足以存储整行数据,则返回 RESULT_BUFFER_FULL。 + * 如果遇到文件结尾,则返回 RESULT_EOF。 + */ template -inline static ParserResult TextReadLine(ReadableParser* self, LineBuffer& buf) +inline static ParserResult TextReadLine(ReadableParser *self, LineBuffer &buf) { bool need_data = false; - char* eol = NULL; + char *eol = NULL; int remainLen; - char* raw_buffer = self->rec_buf; - Source* source = self->source; + char *raw_buffer = self->rec_buf; + Source *source = self->source; int eol_len = (self->eol == NULL) ? 1 : strlen(self->eol); while (true) { - char* end = NULL; + char *end = NULL; // if buffer is empty, read new data from files // @@ -785,13 +1063,28 @@ inline static ParserResult TextReadLine(ReadableParser* self, LineBuffer& buf) self->eol_cur_saved = 0; return RESULT_SUCCESS; } - -inline static ParserResult FixReadLine(FixParser* self, LineBuffer& buf) +/* + * 功能:修复读取行 + * + * 参数列表: + * self:FixParser 指针,表示修复解析器对象 + * buf:LineBuffer 引用,表示用于存储读取的行数据的缓冲区 + * + * 返回值: + * 返回 ParserResult 枚举值,表示读取行的结果,可能为 RESULT_EOF、RESULT_BUFFER_FULL 或 RESULT_SUCCESS。 + * + * 注意: + * 该函数用于修复读取行操作。函数会循环读取数据,并将数据存储到缓冲区 buf 中。 + * 如果缓冲区不足以存储整行数据,则返回 RESULT_BUFFER_FULL。 + * 如果遇到文件结尾,则返回 RESULT_EOF。 + * 如果成功读取行,则返回 RESULT_SUCCESS。 + */ +inline static ParserResult FixReadLine(FixParser *self, LineBuffer &buf) { int remainLen; - char* raw_buffer = NULL; + char *raw_buffer = NULL; int needRead = self->rowSize; - Source* source = self->source; + Source *source = self->source; /* * skip the size of the un-completed row chunk which is already put in LineBuffer; @@ -856,17 +1149,32 @@ inline static ParserResult FixReadLine(FixParser* self, LineBuffer& buf) return RESULT_SUCCESS; } - +/* + * 功能:通用读取行 + * + * 参数列表: + * self:Parser 指针,表示解析器对象 + * + * 返回值: + * 返回 ParserResult 枚举值,表示读取行的结果,可能为 RESULT_EOF、RESULT_BUFFER_FULL 或 RESULT_SUCCESS。 + * + * 注意: + * 该函数用于通用读取行操作,支持不同的文件格式(format)。根据 format 参数的值,选择相应的行读取函数。 + * 函数会循环读取数据,直到行缓冲区 line_buffers 被填满或达到文件结尾。 + * 如果成功读取行,则返回 RESULT_SUCCESS。 + * 如果达到文件结尾且还有更多文件可读取,则返回 RESULT_NEW_ONE。 + * 如果行缓冲区不足以存储整行数据,则返回 RESULT_BUFFER_FULL。 + */ template ParserResult #ifdef GDS_SERVER - GenericReadLines(Parser* self, struct evbuffer& buf) + GenericReadLines(Parser *self, struct evbuffer &buf) #else - GenericReadLines(Parser* self) + GenericReadLines(Parser *self) #endif { - Source* source = self->source; - ReadableParser* parser = (ReadableParser*)self; + Source *source = self->source; + ReadableParser *parser = (ReadableParser *)self; if (parser->eof) return RESULT_EOF; @@ -876,11 +1184,11 @@ ParserResult ParserResult result = RESULT_SUCCESS; if (format == FORMAT_TEXT) - result = TextReadLine((ReadableParser*)self, self->line_buffers); + result = TextReadLine((ReadableParser *)self, self->line_buffers); else if (format == FORMAT_FIXED) - result = FixReadLine((FixParser*)self, self->line_buffers); + result = FixReadLine((FixParser *)self, self->line_buffers); else if (format == FORMAT_CSV) - result = CSVReadLine((CSVParser*)self, self->line_buffers); + result = CSVReadLine((CSVParser *)self, self->line_buffers); if (RESULT_EOF == result) { if (source->SourceNext(source)) { @@ -891,9 +1199,9 @@ ParserResult // if (self->hasHeader) { if (format == FORMAT_TEXT || format == FORMAT_FIXED) - (void)TextReadLine((ReadableParser*)self, self->line_buffers); + (void)TextReadLine((ReadableParser *)self, self->line_buffers); else - (void)CSVReadLine((CSVParser*)self, self->line_buffers); + (void)CSVReadLine((CSVParser *)self, self->line_buffers); } return RESULT_NEW_ONE; } else { @@ -905,18 +1213,35 @@ ParserResult } } } +/* + * 功能:通用写入行 + * + * 参数列表: + * self:Parser 指针,表示解析器对象 + * buf:evbuffer 引用,表示待写入的数据缓冲区 + * len:整数,表示待写入数据的长度 + * + * 返回值: + * 返回 ParserResult 枚举值,表示写入行的结果,通常为 RESULT_SUCCESS。 + * + * 注意: + * 该函数用于通用写入行操作,支持不同的写入方式。 + * 在 GDS_SERVER 模式下,函数会根据数据段大小 `segSize` 来判断是否需要生成新的导出文件, + * 并将数据写入源文件。如果有头部数据 `fileheader`,会在每个新文件中写入头部数据。 + * 在非 GDS_SERVER 模式下,会记录错误信息,表示该路径的代码未实现。 + */ template -ParserResult GenericWriteLines(Parser* self, struct evbuffer& buf, int len) +ParserResult GenericWriteLines(Parser *self, struct evbuffer &buf, int len) { #ifdef GDS_SERVER - Source* source = self->source; + Source *source = self->source; size_t segSize = GetDataSegmentSize(); if (segSize > 0 && source->GetWriteOffset() > segSize) { - source->GenerateNewFileForExport(((WritableParser*)self)->prefix, "dat"); + source->GenerateNewFileForExport(((WritableParser *)self)->prefix, "dat"); if (hasHeader) - source->SourceWrite(((WritableParser*)self)->fileheader, ((WritableParser*)self)->headerSize); + source->SourceWrite(((WritableParser *)self)->fileheader, ((WritableParser *)self)->headerSize); } source->SourceWrite(&buf, len); @@ -925,44 +1250,72 @@ ParserResult GenericWriteLines(Parser* self, struct evbuffer& buf, int len) #endif return RESULT_SUCCESS; } - -static void ReadableParserInit(ReadableParser* self, CmdBegin* cmd, FileList* files, SourceType sourcetype) +/* + * 功能:初始化可读解析器 + * + * 参数列表: + * self:ReadableParser 指针,表示可读解析器对象 + * cmd:CmdBegin 指针,表示命令开始信息 + * files:FileList 指针,表示文件列表信息 + * sourcetype:SourceType 枚举值,表示源类型 + * + * 注意: + * 该函数用于初始化可读解析器对象。根据参数进行初始化操作,包括分配内存、设置属性等。 + */ +static void ReadableParserInit(ReadableParser *self, CmdBegin *cmd, FileList *files, SourceType sourcetype) { + // 初始化文件结束标志为 false,表示文件未结束 self->eof = false; + + // 如果缓冲区长度为 0,则设置为 INITIAL_BUF_LEN if (self->buf_len == 0) self->buf_len = INITIAL_BUF_LEN; - self->rec_buf = (char*)malloc(self->buf_len); + // 分配 rec_buf 缓冲区内存,用于存储读取的数据,大小为 buf_len 字节 + self->rec_buf = (char *)malloc(self->buf_len); if (self->rec_buf == NULL) { parser_log(LEVEL_ERROR, "memory alloc failed!\n"); } + + // 初始化已使用数据长度 used_len、当前位置 cur、EOL 字符当前位置 eol_cur 和 EOL 字符位置保存 eol_cur_saved self->used_len = 0; self->cur = 0; self->eol_cur = 0; self->eol_cur_saved = 0; + // 根据命令中的 m_header 属性,设置是否有头部数据的标志 hasHeader self->hasHeader = cmd->m_header; #ifdef GDS_SERVER + // 如果是服务器模式,创建一个 libevent 的 evbuffer 用于存储行数据 self->line_buffer = evbuffer_new(); if (self->line_buffer == NULL) parser_log(LEVEL_ERROR, "failed to init parser, out of memory"); #endif + + // 创建数据源 source,根据 sourcetype 和 files 参数进行初始化 self->source = CreateSource(files, sourcetype); + + // 如果 sourcetype 不是 SOURCE_TYPE_OBS 并且命令中的 m_prefix 不为 NULL,则复制 m_prefix 到 prefix 属性 if (sourcetype != SOURCE_TYPE_OBS && (cmd->m_prefix != NULL)) { self->prefix = strdup(cmd->m_prefix); if (self->prefix == NULL) parser_log(LEVEL_ERROR, "failed to copy prefix, out of memory"); } + // 再次检查 rec_buf 是否为 NULL,如果是则记录内存分配错误 if (self->rec_buf == NULL) parser_log(LEVEL_ERROR, "failed to init parser, out of memory"); + + // 初始化 rec_buf 的第一个字符为 '\0' self->rec_buf[0] = '\0'; + // 初始化 line_buffers 和相关属性,用于管理行数据 self->line_buffers.Init(); self->cur_need_flush = 0; self->is_cur_line_completed = false; + // 如果命令中的 m_eol 不为 NULL,则复制 m_eol 到 eol 属性 if (cmd->m_eol != NULL) { self->eol = strdup(cmd->m_eol); if (self->eol == NULL) @@ -970,48 +1323,121 @@ static void ReadableParserInit(ReadableParser* self, CmdBegin* cmd, FileList* fi } } -static void CSVParserInit(CSVParser* self, CmdBegin* cmd, FileList* files, SourceType sourcetype) +/* + * 功能:初始化 CSV 解析器 + * + * 参数列表: + * self:CSVParser 指针,表示 CSV 解析器对象 + * cmd:CmdBegin 指针,表示命令开始信息 + * files:FileList 指针,表示文件列表信息 + * sourcetype:SourceType 枚举值,表示源类型 + * + * 注意: + * 该函数用于初始化 CSV 解析器对象。首先调用 ReadableParserInit 进行通用属性初始化,然后根据命令中的 + * m_quote 和 m_escape 属性,设置 CSV 解析器的引用字符和转义字符。如果这些属性为 NULL 或相等,会进行默认设置。 + */ + +static void CSVParserInit(CSVParser *self, CmdBegin *cmd, FileList *files, SourceType sourcetype) { + // 调用通用可读解析器的初始化函数进行通用属性的初始化 ReadableParserInit(self, cmd, files, sourcetype); + + // 设置 CSV 解析器的引用字符和转义字符 self->quote = cmd->m_quote; self->escape = cmd->m_escape; + + // 如果引用字符为空,则默认为双引号 self->quote = self->quote ? self->quote : '"'; + + // 如果转义字符为空,则默认为双引号 self->escape = self->escape ? self->escape : '"'; + + // 如果转义字符与引用字符相等,则将转义字符设置为空字符 self->escape = self->escape == self->quote ? '\0' : self->escape; } -static void FixParserInit(FixParser* self, CmdBegin* cmd, FileList* files, SourceType sourcetype) +/* + * 功能:初始化固定格式解析器 + * + * 参数列表: + * self:FixParser 指针,表示固定格式解析器对象 + * cmd:CmdBegin 指针,表示命令开始信息 + * files:FileList 指针,表示文件列表信息 + * sourcetype:SourceType 枚举值,表示源类型 + * + * 注意: + * 该函数用于初始化固定格式解析器对象。首先调用 ReadableParserInit 进行通用属性初始化,然后根据命令中的 + * m_fixSize 属性,设置固定格式解析器的行大小。如果行大小为 0,则将读取行处理函数设置为 + * GenericReadLines。 + */ + +static void FixParserInit(FixParser *self, CmdBegin *cmd, FileList *files, SourceType sourcetype) { + // 调用通用可读解析器的初始化函数进行通用属性的初始化 ReadableParserInit(self, cmd, files, sourcetype); + + // 设置固定格式解析器的行大小 self->rowSize = cmd->m_fixSize; + + // 如果行大小为 0,则将读取行处理函数设置为 GenericReadLines if (self->rowSize == 0) self->readlines = (ParserReadLineProc)GenericReadLines; } -static void WritableParserInit(WritableParser* self, CmdBegin* cmd, FileList* files) +/* + * 功能:初始化可写解析器 + * + * 参数列表: + * self:WritableParser 指针,表示可写解析器对象 + * cmd:CmdBegin 指针,表示命令开始信息 + * files:FileList 指针,表示文件列表信息 + * 无 + * + * 注意: + * 该函数用于初始化可写解析器对象。首先根据宏定义判断是否支持 GDS_SERVER,然后进行相应的初始化操作。 + * 如果支持 GDS_SERVER,则进行以下操作: + * 1. 设置 hasHeader 为 false。 + * 2. 创建行缓冲区 line_buffer 和源对象 source。 + * 3. 设置源对象的路径为命令中的 URL 经过 UriToLocalPath 处理后的结果。 + * 4. 复制命令中的前缀到 prefix 属性。 + * 5. 如果命令中存在文件头文件,则获取文件头信息,生成新的输出文件,并将文件头写入输出文件。 + * 6. 如果命令中存在换行符属性,则复制到 eol 属性。 + */ + +static void WritableParserInit(WritableParser *self, CmdBegin *cmd, FileList *files) { #ifdef GDS_SERVER + // 设置 hasHeader 为 false self->hasHeader = false; + + // 创建行缓冲区 line_buffer 和源对象 source self->line_buffer = evbuffer_new(); self->source = CreateSource(NULL, SOURCE_TYPE_FILE); + // 检查是否成功创建行缓冲区和源对象 if ((self->line_buffer == NULL) || (self->source == NULL)) parser_log(LEVEL_ERROR, "failed to init parser, out of memory"); + // 设置源对象的路径为命令中的 URL 经过 UriToLocalPath 处理后的结果 self->source->SetPath(UriToLocalPath(cmd->m_url)); + + // 复制命令中的前缀到 prefix 属性 if (cmd->m_prefix == NULL) { parser_log(LEVEL_ERROR, "the given prefix is NULL"); } self->prefix = strdup(cmd->m_prefix); if (self->prefix == NULL) parser_log(LEVEL_ERROR, "failed to copy prefix, out of memory"); - if (cmd->m_fileheader != NULL) - GetFileHeader(self, UriToLocalPath(cmd->m_fileheader).c_str()); - self->source->GenerateNewFileForExport(self->prefix, "dat"); + + // 如果命令中存在文件头文件,则获取文件头信息,生成新的输出文件,并将文件头写入输出文件 if (cmd->m_fileheader != NULL) { + GetFileHeader(self, UriToLocalPath(cmd->m_fileheader).c_str()); + self->source->GenerateNewFileForExport(self->prefix, "dat"); self->source->SourceWrite(self->fileheader, self->headerSize); self->writelines = (ParserWriteLineProc)GenericWriteLines; } + + // 如果命令中存在换行符属性,则复制到 eol 属性 if (cmd->m_eol != NULL) { self->eol = strdup(cmd->m_eol); if (self->eol == NULL) @@ -1022,85 +1448,212 @@ static void WritableParserInit(WritableParser* self, CmdBegin* cmd, FileList* fi #endif } -Parser* CreateCSVParser() +/* + * 功能:创建 CSV 解析器对象 + * + * 返回值: + * 返回创建的 CSV 解析器对象指针 + * + * 注意: + * 该函数用于创建 CSV + * 解析器对象。首先分配内存空间来存储解析器对象,然后进行相应的初始化操作,包括设置函数指针和属性值。 + * 最后,返回创建的解析器对象指针。 + */ + +Parser *CreateCSVParser() { errno_t rc; - CSVParser* self = (CSVParser*)malloc(sizeof(CSVParser)); + // 分配内存空间来存储 CSV 解析器对象 + CSVParser *self = (CSVParser *)malloc(sizeof(CSVParser)); + // 检查内存分配是否成功 if (NULL == self) parser_log(LEVEL_ERROR, "failed to create parser, out of memory"); + // 使用 memset_s 函数将解析器对象的内存清零 rc = memset_s(self, sizeof(CSVParser), 0, sizeof(CSVParser)); parser_securec_check(rc); + + // 设置解析器对象的初始化函数指针为 CSVParserInit self->init = (ParserInitProc)CSVParserInit; + + // 设置解析器对象的读取行函数指针为 GenericReadLines self->readlines = (ParserReadLineProc)GenericReadLines; + + // 设置解析器对象的写入行函数指针为 NopWriteLine self->writelines = (ParserWriteLineProc)NopWriteLine; + + // 设置解析器对象的销毁函数指针为 DestroyReadableParser self->destroy = (ParserDestroyProc)DestroyReadableParser; + + // 设置解析器对象的清理函数指针为 NopCleanup self->cleanup = (ParserDestroyProc)NopCleanup; + + // 初始化解析器对象的属性值 self->in_quote = false; self->lastWasEsc = false; self->in_cr = false; - return (Parser*)self; + + // 返回创建的 CSV 解析器对象指针 + return (Parser *)self; } -Parser* CreateTextParser() +/* + * 功能:创建文本解析器对象 + * + * 参数列表:无 + * + * 返回值: + * 返回创建的文本解析器对象指针 + * + * 注意: + * 该函数用于创建文本解析器对象。首先分配内存空间来存储解析器对象,然后进行相应的初始化操作,包括设置函数指针和属性值。 + * 最后,返回创建的解析器对象指针。 + */ + +Parser *CreateTextParser() { errno_t rc; - ReadableParser* self = (ReadableParser*)malloc(sizeof(ReadableParser)); + // 分配内存空间来存储文本解析器对象 + ReadableParser *self = (ReadableParser *)malloc(sizeof(ReadableParser)); + // 检查内存分配是否成功 if (NULL == self) parser_log(LEVEL_ERROR, "failed to create parser, out of memory"); + // 使用 memset_s 函数将解析器对象的内存清零 rc = memset_s(self, sizeof(ReadableParser), 0, sizeof(ReadableParser)); parser_securec_check(rc); + + // 设置解析器对象的初始化函数指针为 ReadableParserInit self->init = (ParserInitProc)ReadableParserInit; + + // 设置解析器对象的读取行函数指针为 GenericReadLines self->readlines = (ParserReadLineProc)GenericReadLines; + + // 设置解析器对象的写入行函数指针为 NopWriteLine self->writelines = (ParserWriteLineProc)NopWriteLine; + + // 设置解析器对象的销毁函数指针为 DestroyReadableParser self->destroy = (ParserDestroyProc)DestroyReadableParser; + + // 设置解析器对象的清理函数指针为 NopCleanup self->cleanup = (ParserDestroyProc)NopCleanup; - return (Parser*)self; + + // 返回创建的文本解析器对象指针 + return (Parser *)self; } -Parser* CreateFixedParser() +/* + * 功能:创建固定格式解析器对象 + * + * 参数列表:无 + * + * 返回值: + * 返回创建的固定格式解析器对象指针 + * + * 注意: + * 该函数用于创建固定格式解析器对象。首先分配内存空间来存储解析器对象,然后进行相应的初始化操作,包括设置函数指针和属性值。 + * 最后,返回创建的解析器对象指针。 + */ + +Parser *CreateFixedParser() { errno_t rc; - FixParser* self = (FixParser*)malloc(sizeof(FixParser)); + // 分配内存空间来存储固定格式解析器对象 + FixParser *self = (FixParser *)malloc(sizeof(FixParser)); + // 检查内存分配是否成功 if (NULL == self) parser_log(LEVEL_ERROR, "failed to create parser, out of memory"); + // 使用 memset_s 函数将解析器对象的内存清零 rc = memset_s(self, sizeof(FixParser), 0, sizeof(FixParser)); parser_securec_check(rc); + + // 设置解析器对象的初始化函数指针为 FixParserInit self->init = (ParserInitProc)FixParserInit; + + // 设置解析器对象的读取行函数指针为 GenericReadLines self->readlines = (ParserReadLineProc)GenericReadLines; + + // 设置解析器对象的写入行函数指针为 NopWriteLine self->writelines = (ParserWriteLineProc)NopWriteLine; + + // 设置解析器对象的销毁函数指针为 DestroyReadableParser self->destroy = (ParserDestroyProc)DestroyReadableParser; + + // 设置解析器对象的清理函数指针为 NopCleanup self->cleanup = (ParserDestroyProc)NopCleanup; - return (Parser*)self; + + // 返回创建的固定格式解析器对象指针 + return (Parser *)self; } -Parser* CreateWritableParser() +/* + * 功能:创建可写解析器对象 + * + * 参数列表:无 + * + * 返回值: + * 返回创建的可写解析器对象指针 + * + * 注意: + * 该函数用于创建可写解析器对象。首先分配内存空间来存储解析器对象,然后进行相应的初始化操作,包括设置函数指针和属性值。 + * 最后,返回创建的解析器对象指针。 + */ + +Parser *CreateWritableParser() { errno_t rc; - WritableParser* self = (WritableParser*)malloc(sizeof(WritableParser)); + // 分配内存空间来存储可写解析器对象 + WritableParser *self = (WritableParser *)malloc(sizeof(WritableParser)); + // 检查内存分配是否成功 if (NULL == self) parser_log(LEVEL_ERROR, "failed to create parser, out of memory"); + // 使用 memset_s 函数将解析器对象的内存清零 rc = memset_s(self, sizeof(WritableParser), 0, sizeof(WritableParser)); parser_securec_check(rc); + + // 设置解析器对象的初始化函数指针为 WritableParserInit self->init = (ParserInitProc)WritableParserInit; + + // 设置解析器对象的读取行函数指针为 NopReadLine self->readlines = (ParserReadLineProc)NopReadLine; + + // 设置解析器对象的写入行函数指针为 GenericWriteLines self->writelines = (ParserWriteLineProc)GenericWriteLines; + + // 设置解析器对象的销毁函数指针为 DestroyWritableParser self->destroy = (ParserDestroyProc)DestroyWritableParser; + + // 设置解析器对象的清理函数指针为 CleanupWritablParser self->cleanup = (ParserDestroyProc)CleanupWritablParser; - return (Parser*)self; + + // 返回创建的可写解析器对象指针 + return (Parser *)self; } -Parser* CreateParser(FileFormat format) -{ - Parser* parser = NULL; +/* + * 功能:创建解析器对象 + * + * 参数列表: + * - format:文件格式(FORMAT_TEXT、FORMAT_CSV、FORMAT_FIXED、FORMAT_REMOTEWRITE) + * + * 返回值: + * 返回创建的解析器对象指针 + * + * 注意: + * 该函数用于根据文件格式创建相应的解析器对象。根据传入的文件格式,选择对应的创建函数来创建解析器对象,然后返回创建的解析器对象指针。 + */ +Parser *CreateParser(FileFormat format) +{ + Parser *parser = NULL; + + // 根据文件格式选择对应的创建函数来创建解析器对象 switch (format) { case FORMAT_TEXT: parser = CreateTextParser(); @@ -1117,86 +1670,169 @@ Parser* CreateParser(FileFormat format) default: parser_log(LEVEL_ERROR, "un-support format."); } + + // 返回创建的解析器对象指针 return parser; } -static void DestroyParser(Parser* self) +/* + * 功能:销毁解析器对象 + * + * 参数列表: + * - self:要销毁的解析器对象指针 + * + * 注意: + * 该函数用于销毁解析器对象以及相关资源。首先释放解析器对象中的 source、line_buffer、prefix、eol + * 等资源,并最终释放解析器对象自身的内存。 + */ + +static void DestroyParser(Parser *self) { + // 释放 source 对象 if (self->source != NULL) { delete self->source; self->source = NULL; } + + // 释放 line_buffer 对象(仅在 GDS_SERVER 情况下存在) #ifdef GDS_SERVER if (self->line_buffer != NULL) { evbuffer_free(self->line_buffer); self->line_buffer = NULL; } #endif + + // 释放 prefix 字符串 if (self->prefix != NULL) { free(self->prefix); self->prefix = NULL; } + + // 释放 eol 字符串 if (self->eol != NULL) { free(self->eol); self->eol = NULL; } + + // 清理 line_buffers 对象 self->line_buffers.Clean(); + + // 最后释放解析器对象自身的内存 free(self); } -static void DestroyReadableParser(ReadableParser* self) +/* + * 功能:销毁可读解析器对象 + * + * 参数列表: + * - self:要销毁的可读解析器对象指针 + * + * 注意: + * 该函数用于销毁可读解析器对象以及相关资源。首先释放可读解析器对象中的 rec_buf 缓冲区,然后调用 DestroyParser + * 函数释放其他资源,最终将解析器对象自身指针置为 NULL。 + */ + +static void DestroyReadableParser(ReadableParser *self) { + // 释放 rec_buf 缓冲区 if (self->rec_buf != NULL) { free(self->rec_buf); self->rec_buf = NULL; } + + // 调用 DestroyParser 函数释放其他资源 DestroyParser(self); + + // 将解析器对象自身指针置为 NULL self = NULL; } -static void DestroyWritableParser(WritableParser* self) +/* + * 功能:销毁可写解析器对象 + * + * 参数列表: + * - self:要销毁的可写解析器对象指针 + * + * 注意: + * 该函数用于销毁可写解析器对象以及相关资源。首先释放可写解析器对象中的 fileheader 缓冲区,然后调用 SourceFlush + * 函数刷新数据至文件, 最后调用 DestroyParser 函数释放其他资源,最终将解析器对象自身指针置为 NULL。 + */ + +static void DestroyWritableParser(WritableParser *self) { + // 释放 fileheader 缓冲区 if (self->fileheader != NULL) { free(self->fileheader); self->fileheader = NULL; } + // 刷新数据至文件 self->source->SourceFlush(); + + // 调用 DestroyParser 函数释放其他资源 DestroyParser(self); + + // 将解析器对象自身指针置为 NULL self = NULL; } -static void NopCleanup(Parser* self) +static void NopCleanup(Parser *self) {} -static void CleanupWritablParser(WritableParser* self) +/* + * 功能:清理可写解析器对象 + * + * 参数列表: + * - self:要清理的可写解析器对象指针 + * + * 注意: + * 该函数用于清理可写解析器对象,主要工作包括关闭当前文件但不刷新数据(使用 CloseCurrentFileNoFlush + * 函数)和删除未提交的文件。 具体操作包括:获取文件列表,关闭当前文件但不刷新数据,遍历文件列表删除未提交的文件。 + */ + +static void CleanupWritablParser(WritableParser *self) { FileList::iterator i; - FileList* files = self->source->GetFileList(); + FileList *files = self->source->GetFileList(); + + // 关闭当前文件但不刷新数据 self->source->CloseCurrentFileNoFlush(); - // Delete uncommited files - // + // 删除未提交的文件 for (i = files->begin(); i != files->end(); i++) { (void)unlink(i->c_str()); } } #ifdef GDS_SERVER -static void GetFileHeader(WritableParser* self, const char* path) +/* + * 功能:从用户定义的文件头中获取文件头信息 + * + * 参数列表: + * - self:可写解析器对象指针,用于存储文件头信息 + * - path:用户定义的文件头文件路径 + * + * 注意: + * 该函数用于从用户定义的文件头文件中获取文件头信息,主要工作包括:打开文件,读取文件头数据,处理多行文件头,存储文件头信息。 + * 具体操作包括:分配文件头缓冲区,打开文件,读取文件头数据,处理多行文件头,存储文件头信息。 + */ +static void GetFileHeader(WritableParser *self, const char *path) { - FILE* fd = NULL; + FILE *fd = NULL; int nread = 0; - char* eol = NULL; + char *eol = NULL; - self->fileheader = (char*)calloc(1, FILEHEADER_BUF_SIZE + 1); + // 分配文件头缓冲区 + self->fileheader = (char *)calloc(1, FILEHEADER_BUF_SIZE + 1); if (self->fileheader == NULL) parser_log(LEVEL_ERROR, "out of memory"); + // 打开文件 fd = fopen(path, "r"); if (fd == NULL) parser_log(LEVEL_ERROR, "failed to open \"%s\"", path); + // 读取文件头数据 nread = fread(self->fileheader, 1, FILEHEADER_BUF_SIZE, fd); int err_no = ferror(fd); fclose(fd); @@ -1213,6 +1849,7 @@ static void GetFileHeader(WritableParser* self, const char* path) // if the user-define header file has multiple rows, // we will use the first row as the header line only. // + // 如果用户定义的文件头文件有多行,仅使用第一行作为文件头 eol = FindEolChar(self->fileheader, nread, NULL, NULL, NULL); if (eol != NULL) { if (*eol != '\n' && *(eol + 1) == '\n') @@ -1225,54 +1862,106 @@ static void GetFileHeader(WritableParser* self, const char* path) parser_log(LEVEL_ERROR, "user-define header cannot longer than 1MB"); } #endif - +/* + * 功能:初始化行缓冲区 + * + * 注意: + * 该函数用于初始化行缓冲区,主要工作包括: + * 1. 分配行缓冲区内存 + * 2. 初始化缓冲区参数和状态 + * + * 具体操作包括:分配行缓冲区内存,初始化参数和状态。 + */ void GDS::LineBuffer::Init() { + // 设置缓冲区初始大小为 MAX_BLK_SIZE m_buf_len = MAX_BLK_SIZE; - m_buf = (char*)malloc(m_buf_len); + + // 分配缓冲区内存 + m_buf = (char *)malloc(m_buf_len); if (m_buf == NULL) parser_log(LEVEL_ERROR, "failed to init line buffer, out of memory"); + // 初始化已使用长度和行数 m_used_len = 0; m_row_num = 0; + // 初始化当前行指针,当前行长度,当前行是否已完成 m_cur_line = m_buf; m_cur_line_len = 0; m_cur_line_completed = true; + // 初始化输出指针为空 m_output = NULL; #ifdef OBS_SERVER + // 初始化读取位置 m_read_pos = 0; + + // 切换到 OBSParserContext 内存上下文 Assert(u_sess->cmd_cxt.OBSParserContext); MemoryContext oldcontext = MemoryContextSwitchTo(u_sess->cmd_cxt.OBSParserContext); + + // 创建一个新的 StringInfo 对象,用于处理过载缓冲区 m_overload_buf = makeStringInfo(); + + // 切换回原来的内存上下文 MemoryContextSwitchTo(oldcontext); + + // 初始化过载缓冲区是否已完成 m_overload_buf_completed = false; #endif } - +/* + * 功能:重置行缓冲区状态 + * + * 注意: + * 该函数用于重置行缓冲区的状态,主要工作包括: + * 1. 移动未完成的行到缓冲区前部(如果有未完成的行) + * 2. 重置缓冲区参数和状态 + * + * 具体操作包括:移动未完成的行到缓冲区前部(如果有未完成的行),重置参数和状态。 + */ void GDS::LineBuffer::Reset() { if (!m_cur_line_completed && (m_buf != m_cur_line)) { - // move uncomplete line to the front + // 将未完成的行移到缓冲区前部 errno_t rc = memmove_s(m_buf, m_buf_len, m_cur_line, (m_cur_line_len + ROW_HEADER_SIZE)); parser_securec_check(rc); + // 更新已使用长度 m_used_len = m_cur_line_len + ROW_HEADER_SIZE; + + // 重置当前行指针为缓冲区起始位置 m_cur_line = m_buf; } else { + // 未有未完成的行,重新初始化成员变量 m_used_len = 0; m_cur_line = m_buf; m_cur_line_len = 0; } #ifdef OBS_SERVER + // 重置读取位置 m_read_pos = 0; #endif } - -int GDS::LineBuffer::AppendLine(const char* buf, int buf_len, bool isComplete) +/* + * 功能:向行缓冲区追加数据 + * + * 参数列表: + * - buf:要追加到行缓冲区的数据的指针 + * - buf_len:要追加的数据的长度 + * - isComplete:标志是否追加的数据组成了一个完整的行 + * + * 返回值: + * - 返回实际追加到行缓冲区的数据长度,如果追加失败则返回负数 + * + * 注意: + * 该函数用于将数据追加到行缓冲区,主要工作包括:检查参数、检查缓冲区空间、处理数据、更新行头信息、拷贝数据。 + * 具体操作包括:检查参数、检查缓冲区空间、处理数据、更新行头信息、拷贝数据。 + */ +int GDS::LineBuffer::AppendLine(const char *buf, int buf_len, bool isComplete) { if (buf == NULL) return -1; @@ -1340,15 +2029,15 @@ int GDS::LineBuffer::AppendLine(const char* buf, int buf_len, bool isComplete) if (isComplete) { ++m_row_num; char row_header[ROW_HEADER_SIZE]; - *(uint32_t*)&row_header[0] = htonl((uint32_t)(m_cur_line_len + 4)); // add size of row_num - *(uint32_t*)&row_header[4] = htonl(m_row_num); + *(uint32_t *)&row_header[0] = htonl((uint32_t)(m_cur_line_len + 4)); // add size of row_num + *(uint32_t *)&row_header[4] = htonl(m_row_num); ASSERT(m_used_len >= ROW_HEADER_SIZE); rc = memcpy_s(m_cur_line, m_buf_len - (m_cur_line - m_buf), row_header, ROW_HEADER_SIZE); parser_securec_check(rc); } // copy data - char* cur_ptr = m_buf + m_used_len; + char *cur_ptr = m_buf + m_used_len; rc = memcpy_s(cur_ptr, (m_buf_len - m_used_len), buf, buf_len); parser_securec_check(rc); @@ -1358,7 +2047,21 @@ int GDS::LineBuffer::AppendLine(const char* buf, int buf_len, bool isComplete) } #ifdef GDS_SERVER -int GDS::LineBuffer::PackData(evbuffer* dest, bool isFlush) +/* + * 功能:打包数据并发送至目标缓冲区 + * + * 参数列表: + * - dest:目标缓冲区 + * - isFlush:是否要强制发送 + * + * 返回值: + * - 成功返回 0,失败返回错误码 + * + * 注意: + * 该函数用于将数据打包并发送至目标缓冲区,主要工作包括:计算包大小、构建包头、发送数据、重置行缓冲区。 + * 具体操作包括:计算包大小、构建包头、发送数据、重置行缓冲区。 + */ +int GDS::LineBuffer::PackData(evbuffer *dest, bool isFlush) { // 1. compute package size int package_size = (isFlush || m_cur_line_completed) ? m_used_len : (m_cur_line - m_buf); @@ -1372,8 +2075,8 @@ int GDS::LineBuffer::PackData(evbuffer* dest, bool isFlush) if (isFlush && !m_cur_line_completed) { char row_header[ROW_HEADER_SIZE]; - *(uint32_t*)&row_header[0] = htonl((uint32_t)(m_cur_line_len + 4)); // add size of row_num - *(uint32_t*)&row_header[4] = htonl(++m_row_num); + *(uint32_t *)&row_header[0] = htonl((uint32_t)(m_cur_line_len + 4)); // add size of row_num + *(uint32_t *)&row_header[4] = htonl(++m_row_num); rc = memcpy_s(m_cur_line, m_buf_len - (m_cur_line - m_buf), row_header, ROW_HEADER_SIZE); parser_securec_check(rc); @@ -1385,7 +2088,7 @@ int GDS::LineBuffer::PackData(evbuffer* dest, bool isFlush) char package_header[GDSCmdHeaderSize]; package_header[0] = CMD_TYPE_DATA; - *(uint32_t*)&package_header[1] = htonl((uint32_t)package_size); + *(uint32_t *)&package_header[1] = htonl((uint32_t)package_size); // 3. add to evbffer int retval = 0; @@ -1403,8 +2106,23 @@ int GDS::LineBuffer::PackData(evbuffer* dest, bool isFlush) return retval; } - -int GDS::LineBuffer::SendOverloadBuf(evbuffer* dest, const char* buf, int buf_len, bool isComplete) +/* + * 功能:发送溢出缓冲区至目标缓冲区 + * + * 参数列表: + * - dest:目标缓冲区 + * - buf:要发送的数据的指针 + * - buf_len:要发送的数据的长度 + * - isComplete:标志是否要发送的数据组成了一个完整的行 + * + * 返回值: + * - 成功返回 0,失败返回错误码 + * + * 注意: + * 该函数用于将溢出缓冲区的数据发送至目标缓冲区,主要工作包括:更新行头、构建数据包头、发送数据、重置行缓冲区。 + * 具体操作包括:更新行头、构建数据包头、发送数据、重置行缓冲区。 + */ +int GDS::LineBuffer::SendOverloadBuf(evbuffer *dest, const char *buf, int buf_len, bool isComplete) { char row_header[ROW_HEADER_SIZE]; @@ -1414,8 +2132,8 @@ int GDS::LineBuffer::SendOverloadBuf(evbuffer* dest, const char* buf, int buf_le if (isComplete) ++m_row_num; - *(uint32_t*)&row_header[0] = htonl((uint32_t)(m_cur_line_len + buf_len + 4)); // add size of row_num - *(uint32_t*)&row_header[4] = htonl(m_row_num); + *(uint32_t *)&row_header[0] = htonl((uint32_t)(m_cur_line_len + buf_len + 4)); // add size of row_num + *(uint32_t *)&row_header[4] = htonl(m_row_num); /* * Send the buffer; @@ -1427,7 +2145,7 @@ int GDS::LineBuffer::SendOverloadBuf(evbuffer* dest, const char* buf, int buf_le else package_header[0] = CMD_TYPE_DATA_SEG; - *(uint32_t*)&package_header[1] = htonl((uint32_t)(m_cur_line_len + buf_len + ROW_HEADER_SIZE)); + *(uint32_t *)&package_header[1] = htonl((uint32_t)(m_cur_line_len + buf_len + ROW_HEADER_SIZE)); int retval = 0; @@ -1471,13 +2189,28 @@ int GDS::LineBuffer::SendOverloadBuf(evbuffer* dest, const char* buf, int buf_le #ifdef OBS_SERVER #ifndef ENABLE_LITE_MODE -static size_t SourceRead_OBS(Source* self, void* buffer, size_t len) +/* + * 功能:从OBS(Object Storage Service)中读取数据 + * + * 参数列表: + * - self:源对象指针,用于处理OBS读取 + * - buffer:目标缓冲区,用于存储读取到的数据 + * - len:要读取的数据长度 + * + * 返回值: + * - 返回已读取的数据长度 + * + * 注意: + * 该函数用于从OBS中读取数据,主要工作包括:检查是否已读取完毕、创建OBS读取处理器、重复尝试读取数据、标记是否已到达对象的末尾。 + * 具体操作包括:检查是否已读取完毕、创建OBS读取处理器、重复尝试读取数据、标记是否已到达对象的末尾。 + */ +static size_t SourceRead_OBS(Source *self, void *buffer, size_t len) { size_t nread = 0; size_t already_read = 0; if (self->m_obs_end) { - OBSReadWriteHandler* handler = self->GetOBSReadWriteHandler(); + OBSReadWriteHandler *handler = self->GetOBSReadWriteHandler(); if (handler != NULL) { DestroyObsReadWriteHandler(handler, false); @@ -1487,10 +2220,10 @@ static size_t SourceRead_OBS(Source* self, void* buffer, size_t len) return (size_t)0; } - OBSReadWriteHandler* handler = self->GetOBSReadWriteHandler(); + OBSReadWriteHandler *handler = self->GetOBSReadWriteHandler(); if (handler == NULL) { - const char* current_file = self->m_files[self->m_current - 1].c_str(); + const char *current_file = self->m_files[self->m_current - 1].c_str(); handler = CreateObsReadWriteHandler(current_file, OBS_READ, self->m_obs_options); self->SetOBSReadWriteHandler(handler); } @@ -1502,7 +2235,7 @@ static size_t SourceRead_OBS(Source* self, void* buffer, size_t len) PROFILING_OBS_START(); pgstat_report_waitevent(WAIT_EVENT_OBS_READ); do { - nread = read_bucket_object(handler, (char*)buffer + already_read, (len - already_read)); + nread = read_bucket_object(handler, (char *)buffer + already_read, (len - already_read)); already_read += nread; /* nread may be < 0 */ /* Mark we get the end of current object */ @@ -1514,10 +2247,22 @@ static size_t SourceRead_OBS(Source* self, void* buffer, size_t len) return already_read; } - -static bool SourceNext_OBS(Source* self) +/* + * 功能:获取下一个OBS对象 + * + * 参数列表: + * - self:源对象指针,用于处理OBS读取 + * + * 返回值: + * - 如果成功获取下一个OBS对象,则返回true;否则返回false。 + * + * 注意: + * 该函数用于获取下一个OBS对象,主要工作包括:检查是否已到达文件列表末尾、循环遍历文件列表、跳过特殊文件。 + * 具体操作包括:检查是否已到达文件列表末尾、循环遍历文件列表、跳过特殊文件。 + */ +static bool SourceNext_OBS(Source *self) { - const char* current_file = NULL; + const char *current_file = NULL; if ((size_t)(self->m_current) >= self->m_files.size()) return false; @@ -1579,19 +2324,19 @@ bool GDS::LineBuffer::GetNextLine(StringInfo output_line) Assert(m_read_pos < m_used_len); - char* buf = m_buf + m_read_pos; + char *buf = m_buf + m_read_pos; /* Parsing the tuple header part */ char header[ROW_HEADER_SIZE]; errno_t rc = memcpy_s(header, ROW_HEADER_SIZE, buf, ROW_HEADER_SIZE); parser_securec_check(rc); - int64_t tuplen = ntohl(*(uint32_t*)&(header[0])); - int64_t nth = ntohl(*(uint32_t*)&(header[4])); + int64_t tuplen = ntohl(*(uint32_t *)&(header[0])); + int64_t nth = ntohl(*(uint32_t *)&(header[4])); if (nth < 0 || (unsigned int64_t)nth > PG_UINT32_MAX || tuplen < 0 || (unsigned int64_t)tuplen > MaxAllocSize + GDS_HEADER_LEN) { - parser_log( - LEVEL_ERROR, "Linebuffer's content is trashed as tuple's ID %lu and length is not valid %lu", nth, tuplen); + parser_log(LEVEL_ERROR, "Linebuffer's content is trashed as tuple's ID %lu and length is not valid %lu", nth, + tuplen); } /* Minus the tuple id in current line buffer to the actual tuplelen */ @@ -1629,8 +2374,8 @@ void GDS::LineBuffer::MarkLastLineCompleted() /* just like PackData (dest, TRUE) , fill the ROW HEADER of the last uncompleted row, and mark it completed */ char row_header[ROW_HEADER_SIZE]; - *(uint32_t*)&row_header[0] = htonl((uint32_t)(m_cur_line_len + 4)); // add size of row_num - *(uint32_t*)&row_header[4] = htonl(++m_row_num); + *(uint32_t *)&row_header[0] = htonl((uint32_t)(m_cur_line_len + 4)); // add size of row_num + *(uint32_t *)&row_header[4] = htonl(++m_row_num); errno_t rc = memcpy_s(m_cur_line, m_buf_len - (m_cur_line - m_buf), row_header, ROW_HEADER_SIZE); parser_securec_check(rc); @@ -1659,7 +2404,7 @@ void GDS::LineBuffer::MarkLastLineCompleted() * * Important: THIS FOUNCTION JUST USE FOR OBS , DO NOT USE FOR GDS. */ -void GDS::LineBuffer::SaveOverloadBuf(StringInfo dest, const char* buf, int buf_len, bool isComplete) +void GDS::LineBuffer::SaveOverloadBuf(StringInfo dest, const char *buf, int buf_len, bool isComplete) { Assert((0 == m_used_len) || ((false == m_cur_line_completed) && (m_cur_line == m_buf))); diff --git a/src/gausskernel/storage/bulkload/roach_adpter.cpp b/src/gausskernel/storage/bulkload/roach_adpter.cpp index f27ab5a53..95b4a0436 100644 --- a/src/gausskernel/storage/bulkload/roach_adpter.cpp +++ b/src/gausskernel/storage/bulkload/roach_adpter.cpp @@ -32,7 +32,14 @@ template bool getNextRoach(CopyState cstate); template bool getNextRoach(CopyState cstate); template void initRoachState(CopyState cstate, const char *filename, List *totalTask); template void initRoachState(CopyState cstate, const char *filename, List *totalTask); - +/* + * Function: initRoachRoutine + * + * Initializes a RoachRoutine by looking up the "roach_handler" function and + * checking its return type. + * + * Returns: A RoachRoutine pointer. + */ RoachRoutine *initRoachRoutine() { Datum datum; @@ -50,7 +57,16 @@ RoachRoutine *initRoachRoutine() return routine; } - +/* + * Function: getNextRoach + * + * Gets the next Roach data to import or export in a COPY operation. + * + * Parameters: + * - cstate: The COPY operation's state. + * + * Returns: true if successful, false if there's no more data to process. + */ template bool getNextRoach(CopyState cstate) { @@ -78,7 +94,19 @@ bool getNextRoach(CopyState cstate) cstate->roach_context = roach_context; return true; } - +/* + * Function: copyGetRoachData + * + * Reads Roach data for a COPY operation. + * + * Parameters: + * - cstate: The COPY operation's state. + * - databuf: The data buffer to read into. + * - minread: The minimum number of bytes to read. + * - maxread: The maximum number of bytes to read. + * + * Returns: The number of bytes read. + */ int copyGetRoachData(CopyState cstate, void *databuf, int minread, int maxread) { Assert(cstate->roach_routine); @@ -91,7 +119,16 @@ int copyGetRoachData(CopyState cstate, void *databuf, int minread, int maxread) return bytesread; } - +/* + * Function: initRoachState + * + * Initializes the state for a Roach COPY operation. + * + * Parameters: + * - cstate: The COPY operation's state. + * - filename: The Roach file to import/export. + * - totalTask: The total tasks to process. + */ template void initRoachState(CopyState cstate, const char *filename, List *totalTask) { @@ -117,8 +154,8 @@ void initRoachState(CopyState cstate, const char *filename, List *totalTask) char roachPath[PATH_MAX + 1]; const char *pos = strstr(filename, ROACH_PREFIX); pos += ROACH_PREFIX_LEN; - errno_t ret = snprintf_s(roachPath, sizeof(roachPath), PATH_MAX, "%s/%s", pos, - g_instance.attr.attr_common.PGXCNodeName); + errno_t ret = + snprintf_s(roachPath, sizeof(roachPath), PATH_MAX, "%s/%s", pos, g_instance.attr.attr_common.PGXCNodeName); securec_check_ss(ret, "", ""); roachPath[PATH_MAX] = '\0'; @@ -137,7 +174,14 @@ void initRoachState(CopyState cstate, const char *filename, List *totalTask) (void)getNextRoach(cstate); } } - +/* + * Function: endRoachBulkLoad + * + * Ends a Roach bulk load operation. + * + * Parameters: + * - cstate: The COPY operation's state. + */ void endRoachBulkLoad(CopyState cstate) { if (IS_PGXC_DATANODE) { @@ -149,7 +193,14 @@ void endRoachBulkLoad(CopyState cstate) ereport(ERROR, (errcode_for_file_access(), errmsg("could not close roach %s", cstate->filename))); } } - +/* + * Function: exportRoach + * + * Exports data to Roach in a COPY operation. + * + * Parameters: + * - cstate: The COPY operation's state. + */ void exportRoach(CopyState cstate) { Assert(cstate->copy_dest == COPY_ROACH); @@ -196,7 +247,15 @@ void exportRoach(CopyState cstate) resetStringInfo(in); } - +/* + * Function: exportRoachFlushOut + * + * Flushes the data to Roach in a COPY operation. + * + * Parameters: + * - cstate: The COPY operation's state. + * - isWholeLineAtEnd: Indicates if the whole line is at the end. + */ void exportRoachFlushOut(CopyState cstate, bool isWholeLineAtEnd) { Assert(cstate->roach_routine); diff --git a/src/gausskernel/storage/bulkload/vecforeignroutine.cpp b/src/gausskernel/storage/bulkload/vecforeignroutine.cpp index f7e46dafa..13c39a6e2 100644 --- a/src/gausskernel/storage/bulkload/vecforeignroutine.cpp +++ b/src/gausskernel/storage/bulkload/vecforeignroutine.cpp @@ -52,40 +52,88 @@ extern void SyncBulkloadStates(CopyState cstate); extern void CleanBulkloadStates(); // all stuffs used for bulkload(end). // Try to save importing error if needed +/* + * 功能:尝试保存导入错误 + * + * 参数列表: + * importState:导入执行状态指针,包含导入过程中的相关信息 + * node:外部扫描状态指针,用于访问外部扫描相关信息 + * + * 返回值: + * 如果成功保存导入错误,返回true;否则返回false。 + * + * 注意: + * 这个函数用于尝试保存导入错误,它检查错误码是否为数据异常(Data Exception)并且是否可以接受错误。 + * 如果满足条件,它会保存错误记录,并在必要时清除错误状态。 + */ bool TrySaveImportError(DistImportExecutionState *importState, ForeignScanState *node) { + // 检查当前错误码是否为数据异常(Data Exception) if ((ERRCODE_TO_CATEGORY((unsigned int)geterrcode()) == ERRCODE_DATA_EXCEPTION) && DoAcceptOneError(importState)) { + // 如果是数据异常并且DoAcceptOneError返回true,表示可以接受错误 + + // 检查错误码是否为字符不在字符集中或者无法翻译的字符 if (geterrcode() == ERRCODE_CHARACTER_NOT_IN_REPERTOIRE || geterrcode() == ERRCODE_UNTRANSLATABLE_CHARACTER) t_thrd.bulk_cxt.illegal_character_err_cnt++; ListCell *lc = NULL; + // 遍历错误记录器列表,处理每个错误记录 foreach (lc, importState->elogger) { ImportErrorLogger *elogger = (ImportErrorLogger *)lfirst(lc); FormAndSaveImportError(importState, importState->errLogRel, importState->beginTime, elogger); } - // clear error state - // + // 清除错误状态 FlushErrorStateWithoutDeleteChildrenContext(); - return true; + return true; // 返回true表示成功保存导入错误 } - return false; + return false; // 返回false表示没有保存导入错误 } - +/* + * 功能:尝试保存导入错误 + * + * 参数列表: + * cstate:复制状态指针,包含复制过程中的相关信息 + * + * 返回值: + * 如果成功保存导入错误,返回true;否则返回false。 + * + * 注意: + * 这个函数用于尝试保存导入错误,它检查错误码是否为数据异常(Data Exception)并且是否可以接受错误。 + * 如果满足条件,它会保存错误记录,并在必要时清除错误状态。 + */ bool TrySaveImportError(CopyState cstate) { + // 增加错误行数计数器 cstate->errorrows++; - if ((ERRCODE_TO_CATEGORY((unsigned int)geterrcode()) == ERRCODE_DATA_EXCEPTION) && DoAcceptOneError(cstate)) { - FormAndSaveImportError(cstate, cstate->err_table, cstate->copy_beginTime, cstate->logger); - // clear error state - // - FlushErrorStateWithoutDeleteChildrenContext(); - return true; - } - return false; -} + // 检查当前错误码是否为数据异常(Data Exception) + if ((ERRCODE_TO_CATEGORY((unsigned int)geterrcode()) == ERRCODE_DATA_EXCEPTION) && DoAcceptOneError(cstate)) { + // 如果是数据异常并且DoAcceptOneError返回true,表示可以接受错误 + + // 调用函数保存导入错误 + FormAndSaveImportError(cstate, cstate->err_table, cstate->copy_beginTime, cstate->logger); + + // 清除错误状态 + FlushErrorStateWithoutDeleteChildrenContext(); + return true; // 返回true表示成功保存导入错误 + } + return false; // 返回false表示没有保存导入错误 +} +/* + * 功能:执行向量化外部数据导入 + * + * 参数列表: + * node:向量化外部扫描状态指针,包含导入过程中的相关信息 + * + * 返回值: + * 返回包含导入数据的向量批次(VectorBatch)。 + * + * 注意: + * 这个函数用于执行向量化的外部数据导入操作。它从外部数据源中读取数据并将其填充到VectorBatch中。 + * 同时,它还处理错误并保存导入错误记录。 + */ VectorBatch *distExecVecImport(VecForeignScanState *node) { DistImportExecutionState *importState = (DistImportExecutionState *)node->fdw_state; @@ -97,60 +145,45 @@ VectorBatch *distExecVecImport(VecForeignScanState *node) MemoryContext oldMemoryContext; MemoryContext scanMcxt = node->scanMcxt; - /* Set up callback to identify error line number. */ + /* 设置错误回调以识别错误行号 */ errcontext.callback = BulkloadErrorCallback; errcontext.arg = (void *)importState; errcontext.previous = t_thrd.log_cxt.error_context_stack; t_thrd.log_cxt.error_context_stack = &errcontext; - /* - * The protocol for loading a virtual tuple into a slot is first - * ExecClearTuple, then fill the values/isnull arrays, then - * ExecStoreVirtualTuple. If we don't find another row in the file, we - * just skip the last step, leaving the slot empty as required. - * - * We can pass ExprContext = NULL because we read all columns from the - * file, so no need to evaluate default expressions. - * - * We can also pass tupleOid = NULL because we don't allow oids for - * foreign tables. - */ batch->Reset(true); + if (node->m_done) { - /* Remove error callback. */ + /* 移除错误回调 */ t_thrd.log_cxt.error_context_stack = errcontext.previous; return batch; } MemoryContextReset(scanMcxt); oldMemoryContext = MemoryContextSwitchTo(scanMcxt); -#ifndef ENABLE_LITE_MODE - SetObsMemoryContext(((CopyState)importState)->copycontext); -#endif + for (batch->m_rows = 0; batch->m_rows < BatchMaxSize; batch->m_rows++) { -retry: + retry: PG_TRY(); { - /* - * Synchronize the current bulkload states. - */ + /* 同步当前批量加载状态 */ SyncBulkloadStates((CopyState)importState); + + // 从外部数据源中读取下一行数据 found = NextCopyFrom((CopyState)importState, NULL, values, nulls, NULL); } PG_CATCH(); { - /* - * Clean the current bulkload states. - */ - CleanBulkloadStates(); + /* 清理当前批量加载状态 */ + // 尝试保存导入错误,如果成功则重试 if (TrySaveImportError(importState, node)) { (void)MemoryContextSwitchTo(scanMcxt); MemoryContextReset(scanMcxt); CHECK_FOR_INTERRUPTS(); goto retry; } else { - /* clean copy state and re throw */ + /* 清理复制状态并重新抛出异常 */ importState->isExceptionShutdown = true; EndDistImport(importState); PG_RE_THROW(); @@ -158,13 +191,13 @@ retry: } PG_END_TRY(); - /* - * Clean the current bulkload states. - */ + /* 清理当前批量加载状态 */ CleanBulkloadStates(); if (found) { int rows = batch->m_rows; + + // 将读取的数据填充到VectorBatch中 for (int i = 0; i < batch->m_cols; i++) { ScalarVector *vec = &(batch->m_arr[i]); if (nulls[i]) { @@ -187,7 +220,8 @@ retry: } (void)MemoryContextSwitchTo(oldMemoryContext); - /* Remove error callback. */ + + /* 移除错误回调 */ t_thrd.log_cxt.error_context_stack = errcontext.previous; return batch;