代码注释 #36
|
|
@ -231,8 +231,20 @@ void print_parameters(const QueryDesc *queryDesc, ExplainState es)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* exec_explain_plan
|
||||
* parameter: queryDesc
|
||||
* QueryDesc contains query information, such as operation type,
|
||||
* query plan tree, and execution status.
|
||||
*
|
||||
* This function outputs the EXPLAIN information for the query
|
||||
* plan and, if the EXPLAIN ANALYZE option is enabled, collects
|
||||
* and displays execution statistics.
|
||||
*
|
||||
*/
|
||||
void exec_explain_plan(QueryDesc *queryDesc)
|
||||
{
|
||||
/* Store interpretation information*/
|
||||
ExplainState es;
|
||||
if (is_valid_query(queryDesc) && auto_explain_plan()) {
|
||||
INSTR_TIME_SET_CURRENT(plan_time);
|
||||
|
|
@ -243,14 +255,19 @@ void exec_explain_plan(QueryDesc *queryDesc)
|
|||
es.verbose = true;
|
||||
es.analyze = false;
|
||||
es.timing = false;
|
||||
/* Save the current performance mode*/
|
||||
int old_explain_perf_mode = t_thrd.explain_cxt.explain_perf_mode;
|
||||
/* Set the performance mode in the current thread to EXPLAIN_NORMAL
|
||||
* to output the explain information in normal mode*/
|
||||
t_thrd.explain_cxt.explain_perf_mode = (int)EXPLAIN_NORMAL;
|
||||
appendStringInfo(es.str, "\n---------------------------"
|
||||
"-NestLevel:%d----------------------------\n", u_sess->exec_cxt.nesting_level);
|
||||
ExplainQueryText(&es, queryDesc);
|
||||
appendStringInfo(es.str, "Name: %s\n", g_instance.attr.attr_common.PGXCNodeName);
|
||||
ExplainBeginOutput(&es);
|
||||
/* Save the current memory information*/
|
||||
MemoryContext current_ctx = CurrentMemoryContext;
|
||||
/* exception handling*/
|
||||
PG_TRY();
|
||||
{
|
||||
ExplainPrintPlan(&es, queryDesc);
|
||||
|
|
@ -258,6 +275,7 @@ void exec_explain_plan(QueryDesc *queryDesc)
|
|||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
/*Switch to previously saved memory information to avoid memory leaks*/
|
||||
MemoryContextSwitchTo(current_ctx);
|
||||
ErrorData* edata = CopyErrorData();
|
||||
FlushErrorState();
|
||||
|
|
|
|||
|
|
@ -591,7 +591,8 @@ void InstrEndLoop(Instrumentation* instr)
|
|||
/* Skip if nothing has happened, or already shut down */
|
||||
if (!instr->running)
|
||||
return;
|
||||
|
||||
|
||||
/*If the "starttime" field of the node is not 0, the node has not called the InstrStopNode function to stop the timing, and an error may have occurred*/
|
||||
if (!INSTR_TIME_IS_ZERO(instr->starttime)) {
|
||||
elog(DEBUG2, "InstrEndLoop called on running node");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,9 @@ static TupleTableSlot* IndexNext(IndexScanState* node)
|
|||
break;
|
||||
}
|
||||
} else {
|
||||
/* Depending on how the table is stored, call scan_handler_idx_getnext
|
||||
*to select a different API and return the result to the tuple
|
||||
*/
|
||||
if ((tuple = scan_handler_idx_getnext(scandesc, direction)) == NULL) {
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -189,6 +189,25 @@ static TupleTableSlot* SeqNext(SeqScanState* node);
|
|||
|
||||
static void ExecInitNextPartitionForSeqScan(SeqScanState* node);
|
||||
|
||||
/* ------------------------------------------------------
|
||||
* seq_scan_getnext_template
|
||||
* ------------------------------------------------------
|
||||
* Param:
|
||||
* scan:the description of the status of atable scan
|
||||
* slot:tores the tuple obtained from the table
|
||||
* direction:Indicates the direction of the table scan
|
||||
* ------------------------------------------------------
|
||||
* Note:
|
||||
* First determine how to read tuples (1) hashBucket mode
|
||||
* (2) heap mode (3) uheap mode (UHeap provides a different
|
||||
* data storage and management mode from the traditional Heap
|
||||
* It may contain some optimizations and improvements to improve
|
||||
* the performance of certain types of database workloads).
|
||||
* Then call each mode of the read function, read a tuple.
|
||||
* Finally, call UHeapSlotStoreUHeapTuple or heap_slot_store_heap_tuple
|
||||
* to store the returned tuple tuple into the slot.
|
||||
* ------------------------------------------------------
|
||||
*/
|
||||
template<TableAmType type, bool hashBucket>
|
||||
FORCE_INLINE
|
||||
void seq_scan_getnext_template(TableScanDesc scan, TupleTableSlot* slot, ScanDirection direction)
|
||||
|
|
@ -288,6 +307,19 @@ void ExecStoreTupleBatchMode(TableScanDesc scanDesc, TupleTableSlot** slot)
|
|||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------
|
||||
* SeqNextBatchMode
|
||||
* --------------------------------------------------------
|
||||
* Param:
|
||||
* node:SeqScanState类型的结点,表示扫描状态
|
||||
* --------------------------------------------------------
|
||||
* Note:
|
||||
* First, obtain some status and description information of
|
||||
* the scanned node, then update the node information, and
|
||||
* then call ExecStoreTupleBatchMode to store the read
|
||||
* utuple into the slot.
|
||||
* --------------------------------------------------------
|
||||
*/
|
||||
static ScanBatchResult *SeqNextBatchMode(SeqScanState *node)
|
||||
{
|
||||
TableScanDesc scanDesc;
|
||||
|
|
@ -301,10 +333,11 @@ static ScanBatchResult *SeqNextBatchMode(SeqScanState *node)
|
|||
direction = estate->es_direction;
|
||||
slot = &(node->scanBatchState->scanBatch.scanTupleSlotInBatch[0]);
|
||||
|
||||
/* get tuples from the table. */
|
||||
/* update node information */
|
||||
scanDesc->rs_maxScanRows = node->scanBatchState->scanTupleSlotMaxNum;
|
||||
node->scanBatchState->scanfinished = tableam_scan_gettuplebatchmode(scanDesc, direction);
|
||||
|
||||
/* store the read utuple into the slot */
|
||||
if (slot[0]->tts_tupslotTableAm == TAM_USTORE) {
|
||||
ExecStoreTupleBatchMode<TAM_USTORE>(scanDesc, slot);
|
||||
} else {
|
||||
|
|
@ -382,13 +415,41 @@ void SeqScan_Init(TableScanDesc scan, SeqScanAccessor* p_accessor, Relation rela
|
|||
SeqScan_Pref_Quantity(scan, p_accessor, relation);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------
|
||||
* reset_scan_qual
|
||||
* -----------------------------------------------------------------
|
||||
* Param:
|
||||
* curr_heap_rel: current realation stored in heap storage
|
||||
* node: ScanState type structure which indicates the scanning status
|
||||
* isRangeScanInRedis: if it is a range scan in the redistribution process
|
||||
* -----------------------------------------------------------------
|
||||
* Note:
|
||||
* This code basically returns a RangeScanInRedis structure, which
|
||||
* stores three fields.You can see the specific comments in the
|
||||
* struct definition.
|
||||
*
|
||||
* First, if node is empty, the plain value is returned. Then,
|
||||
* if the cluster size is allowed and the relation is in
|
||||
* redistribution process,the init function gets the new qual and
|
||||
* selects a different qual based on whether or not the node is
|
||||
* vectorized reading (call ExecInitVecExpr if ScanBatch, Otherwise
|
||||
* call ExecInitExpr); If the qual field in node has not been
|
||||
* initialized, different qual initialization functions are called
|
||||
* according to whether the node is reading vectorized (ExecInitVecExpr
|
||||
* is called if it is canBatch, and ExecInitExpr is called Sotherwise).
|
||||
* Concatenate rangeScanInRedis to the plain value. Finally return
|
||||
* node->rangeScanInRedis.
|
||||
* -----------------------------------------------------------------
|
||||
*/
|
||||
RangeScanInRedis reset_scan_qual(Relation curr_heap_rel, ScanState* node, bool isRangeScanInRedis)
|
||||
{
|
||||
if (node == NULL) {
|
||||
return {isRangeScanInRedis, 0, 0};
|
||||
}
|
||||
/* if the cluster size is allowed and the relation is in redistribution process */
|
||||
if (u_sess->attr.attr_sql.enable_cluster_resize && RelationInRedistribute(curr_heap_rel)) {
|
||||
List* new_qual = eval_ctid_funcs(curr_heap_rel, node->ps.plan->qual, &node->rangeScanInRedis);
|
||||
/* select a different Init function depending on whether it is ScanBatch */
|
||||
if (!node->scanBatchMode) {
|
||||
node->ps.qual = (List*)ExecInitExpr((Expr*)new_qual, (PlanState*)&node->ps);
|
||||
} else {
|
||||
|
|
@ -396,6 +457,7 @@ RangeScanInRedis reset_scan_qual(Relation curr_heap_rel, ScanState* node, bool i
|
|||
}
|
||||
node->ps.qual_is_inited = true;
|
||||
} else if (!node->ps.qual_is_inited) {
|
||||
/* select a different Init function depending on whether it is ScanBatch */
|
||||
if (!node->scanBatchMode) {
|
||||
node->ps.qual = (List*)ExecInitExpr((Expr*)node->ps.plan->qual, (PlanState*)&node->ps);
|
||||
} else {
|
||||
|
|
@ -407,6 +469,23 @@ RangeScanInRedis reset_scan_qual(Relation curr_heap_rel, ScanState* node, bool i
|
|||
return node->rangeScanInRedis;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------
|
||||
* InitBeginScan
|
||||
* -------------------------------------------------------------
|
||||
* Param:
|
||||
* node: SeqScanState Type structure, which represents scan status information
|
||||
* current_relation: current relation to handle
|
||||
* -------------------------------------------------------------
|
||||
* Note:
|
||||
* This code is used to initialize the Scan information (ScanDesc),
|
||||
* and it depends on whether the node corresponds to Sample Scan or
|
||||
* not to select a different initialization function. (Sample Scan
|
||||
* is a scanning mode that can extract some samples from a query with
|
||||
* a large amount of data, query these samples, and then calculate
|
||||
* the query cost and optimize the query method according to the
|
||||
* query cost.)
|
||||
* -------------------------------------------------------------
|
||||
*/
|
||||
static TableScanDesc InitBeginScan(SeqScanState* node, Relation current_relation)
|
||||
{
|
||||
TableScanDesc current_scan_desc = NULL;
|
||||
|
|
@ -466,6 +545,19 @@ TableScanDesc BeginScanRelation(SeqScanState* node, Relation relation, Transacti
|
|||
return current_scan_desc;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------
|
||||
* GetPartitionPruningResultInInitScanRelation
|
||||
* -------------------------------------------------------------
|
||||
* Param:
|
||||
* plan: SeqScan type structure which indicates the node type
|
||||
* estate: Execution State
|
||||
* current_relation: current relation need execute
|
||||
* -------------------------------------------------------------
|
||||
* Note:
|
||||
* return a PruningResult pointer, which store related
|
||||
* information about partition running result
|
||||
*
|
||||
*/
|
||||
static PruningResult *GetPartitionPruningResultInInitScanRelation(SeqScan *plan, EState *estate,
|
||||
Relation current_relation)
|
||||
{
|
||||
|
|
@ -621,6 +713,18 @@ void InitScanRelation(SeqScanState* node, EState* estate, int eflags)
|
|||
ExecAssignScanType(node, RelationGetDescr(current_relation));
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* InitRelationBatchScanEnv
|
||||
* ----------------------------------------------------------------
|
||||
* Param:
|
||||
* state: structure of type SeqScanState,which contains state
|
||||
* information when excuting
|
||||
* ----------------------------------------------------------------
|
||||
* Note:
|
||||
* Initializes the column number that needs to be projected, and
|
||||
* sets the column number that needs to be late read.
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
static void InitRelationBatchScanEnv(SeqScanState *state)
|
||||
{
|
||||
/* so use MemContext which is not freed at all until the end. */
|
||||
|
|
@ -632,6 +736,7 @@ static void InitRelationBatchScanEnv(SeqScanState *state)
|
|||
return;
|
||||
}
|
||||
|
||||
/* Gets the column that needs to be projected */
|
||||
List *pColList = proj->pi_acessedVarNumbers;
|
||||
|
||||
batchstate->colNum = list_length(pColList);
|
||||
|
|
@ -749,6 +854,44 @@ static SeqScanState *ExecInitSeqScanBatchMode(SeqScan *node, SeqScanState* scans
|
|||
return scanstate;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* InitSeqNextMtd
|
||||
* ----------------------------------------------------------------
|
||||
* Param:
|
||||
* node:
|
||||
* scanstate:
|
||||
* ----------------------------------------------------------------
|
||||
* Note:
|
||||
* This code initializes the ScanNextMtd and fillNextSlotFunc
|
||||
* members of the SeqScanState struct. These two members define
|
||||
* how to get the next record and how to fill the next slot
|
||||
* respectively.
|
||||
*
|
||||
* Specifically, the code first checks whether node->tablesample
|
||||
* is NULL. If NULL, it means that this is not a table sample scan,
|
||||
* but a regular sequential scan. In this case, it sets ScanNextMtd
|
||||
* to funciton SeqNext, which means that the usual sequential scan
|
||||
* method is used to get the next record.
|
||||
*
|
||||
* Then checks whether the current relationship has a bucket
|
||||
* (by calling the RELATION_OWN_BUCKET). If the current relationship
|
||||
* owns the bucket, it selects the appropriate fill slot function
|
||||
* based on the storage type (specified by rd_tam_type).
|
||||
*
|
||||
* If the current relationship does not own the bucket, then the
|
||||
* choice of fill slot function is similar to the above.
|
||||
*
|
||||
* If node->tablesample is not NULL, then this is a tablesample
|
||||
* scan. In this case, if the current relationship owns the bucket,
|
||||
* set ScanNextMtd to function HbktSeqSampleNext; Otherwise, set
|
||||
* it to function SeqSampleNext.
|
||||
*
|
||||
* In general, the purpose of this code is to initialize the
|
||||
* sequential scan state based on the type of scan (regular
|
||||
* sequential scan or table sample scan) and whether the current
|
||||
* relationship owns the bucket.
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
static inline void InitSeqNextMtd(SeqScan* node, SeqScanState* scanstate)
|
||||
{
|
||||
if (!node->tablesample) {
|
||||
|
|
|
|||
|
|
@ -2148,6 +2148,21 @@ static BlockNumber HeapParallelscanNextpage(HeapScanDesc scan)
|
|||
return page;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
* heap_getnext
|
||||
* -----------------------------------------------------------
|
||||
* Param:
|
||||
* sscan:the description of the status of a table scan
|
||||
* direction:the description of the direction of the table scan
|
||||
* -----------------------------------------------------------
|
||||
* Note:
|
||||
* If rs_pageatatime is true, then the heapgettup_pagemode function is called
|
||||
* to read one page, otherwise the heapgettup function is called to read
|
||||
* one tuple at a time.
|
||||
* If the next tuple scanned is empty, returne NULL.
|
||||
* Finally, increase the number of returned tuples by 1
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
HeapTuple heap_getnext(TableScanDesc sscan, ScanDirection direction)
|
||||
{
|
||||
HeapScanDesc scan = (HeapScanDesc) sscan;
|
||||
|
|
@ -2170,7 +2185,8 @@ HeapTuple heap_getnext(TableScanDesc sscan, ScanDirection direction)
|
|||
* the proper return buffer and return the tuple.
|
||||
*/
|
||||
HEAPDEBUG_3; /* heap_getnext returning tuple */
|
||||
|
||||
|
||||
/* increase the number of returned tuples by 1 */
|
||||
pgstat_count_heap_getnext(scan->rs_base.rs_rd);
|
||||
|
||||
Assert(!HEAP_TUPLE_IS_COMPRESSED(scan->rs_ctup.t_data));
|
||||
|
|
|
|||
|
|
@ -54,6 +54,24 @@
|
|||
#include "storage/tcap.h"
|
||||
#include "catalog/pg_constraint.h"
|
||||
|
||||
/*
|
||||
* TvIsContainsForeignKey() ---
|
||||
* Checks whether a given relation (table) with the specified
|
||||
* OID (object identifier) contains a foreign key constraint.
|
||||
*
|
||||
* The expected behavior of this function is to return 'true' if
|
||||
* the relationship specified by 'relid' contains foreign key
|
||||
* constraints, and 'false' otherwise. The implementation of this
|
||||
* function would involve querying the catalog table of the database
|
||||
* system to check for foreign key constraints associated with a
|
||||
* given relational OID.
|
||||
*
|
||||
* Param [IN] relid: It accepts a parameter called 'relid' of type
|
||||
* 'Oid'. 'Oid' is commonly used in database systems to represent
|
||||
* object identifiers.
|
||||
* Returns [OUT]: A variable of type bool. True indicates that it
|
||||
* contains foreign keys.
|
||||
*/
|
||||
static bool TvIsContainsForeignKey(Oid relid)
|
||||
{
|
||||
Relation rbRel;
|
||||
|
|
@ -69,6 +87,12 @@ static bool TvIsContainsForeignKey(Oid relid)
|
|||
|
||||
sd = systable_beginscan(rbRel, ConstraintRelidIndexId, true, SnapshotNow, 1, &key);
|
||||
|
||||
/* For each tuple, check that the constraint type (contype) is
|
||||
* CONSTRAINT_FOREIGN and that the constraint's relationship
|
||||
* identifier (conrelid) matches the relid of the input. If
|
||||
* both conditions are met, the relationship contains a foreign
|
||||
* key constraint, so set isContainsForeignKey to true and break
|
||||
* out of the loop. */
|
||||
while ((tup = systable_getnext(sd)) != NULL) {
|
||||
Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tup);
|
||||
|
||||
|
|
@ -85,6 +109,23 @@ static bool TvIsContainsForeignKey(Oid relid)
|
|||
return isContainsForeignKey;
|
||||
}
|
||||
|
||||
/*
|
||||
* TvIsReferencedByForeignKey() ---
|
||||
* Checks whether the specified relationship (table) is referenced
|
||||
* by a foreign key.
|
||||
*
|
||||
* Foreign key reference relationships are often used to ensure data
|
||||
* integrity, they indicate that a column in one table references a
|
||||
* column in another table, and those references need to follow
|
||||
* specific constraint rules.
|
||||
*
|
||||
* Param [IN] relid: This is the argument list of the function, which
|
||||
* accepts a parameter named 'relid' of type 'Oid', the object
|
||||
* identifier. This parameter is used to specify the identifier
|
||||
* of the relationship (table) to be checked.
|
||||
* Returns [OUT]: Return 'true' if the relation is referenced by a
|
||||
* foreign key, or 'false' otherwise.
|
||||
*/
|
||||
static bool TvIsReferencedByForeignKey(Oid relid)
|
||||
{
|
||||
Relation rbRel;
|
||||
|
|
@ -96,6 +137,10 @@ static bool TvIsReferencedByForeignKey(Oid relid)
|
|||
|
||||
sd = systable_beginscan(rbRel, InvalidOid, false, SnapshotNow, 0, NULL);
|
||||
|
||||
/* For each tuple, check that the constraint's reference relation
|
||||
* identifier (confrelid) matches the input relid. If the condition
|
||||
* is met, the relation is referenced by a foreign key constraint,
|
||||
* so set isReferencedByForeignKey to true and out of the loop. */
|
||||
while ((tup = systable_getnext(sd)) != NULL) {
|
||||
Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tup);
|
||||
|
||||
|
|
@ -117,6 +162,27 @@ static bool TvForeignKeyCheck(Oid relid)
|
|||
return (TvIsContainsForeignKey(relid) || TvIsReferencedByForeignKey(relid));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* TvFeatureSupport() ---
|
||||
* Its function is to check whether a table supports the timecapsule
|
||||
* feature. timecapsule is a feature that allows tables to backtrack data
|
||||
* at different points in time.
|
||||
*
|
||||
* This function determines whether the table to be checked supports
|
||||
* the timecapsule feature based on a set of criteria, and returns the
|
||||
* corresponding result and error message (if any).
|
||||
*
|
||||
* Param [IN] relid: an integer parameter that represents the OID (object
|
||||
* identifier) of the table to be checked.
|
||||
* Param [IN] errstr: a pointer to a string pointer that returns an error message.
|
||||
* Param [IN] isTimecapsuleTable: A Boolean parameter that indicates whether
|
||||
* the table to check has the timecapsule feature enabled. If it is,
|
||||
* more stringent checks are carried out, such as not including or
|
||||
* referencing foreign keys.
|
||||
* Returns [OUT]: True indicates the table to check supports timecapsule related
|
||||
* features, otherwise return false.
|
||||
*/
|
||||
static bool TvFeatureSupport(Oid relid, char **errstr, bool isTimecapsuleTable)
|
||||
{
|
||||
Relation rel = RelationIdGetRelation(relid);
|
||||
|
|
@ -128,6 +194,7 @@ static bool TvFeatureSupport(Oid relid, char **errstr, bool isTimecapsuleTable)
|
|||
errmsg("could not open relation with OID %u", relid)));
|
||||
}
|
||||
|
||||
/* perform a series of correlation checks */
|
||||
classForm = rel->rd_rel;
|
||||
if (classForm->relkind != RELKIND_RELATION) {
|
||||
*errstr = "timecapsule feature does not support non-ordinary table";
|
||||
|
|
@ -164,7 +231,18 @@ static bool TvFeatureSupport(Oid relid, char **errstr, bool isTimecapsuleTable)
|
|||
return *errstr == NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* TvCheckVersionScan() ---
|
||||
* This function is to check if a table supports the timecapsule feature
|
||||
* and report an error if it does not.
|
||||
*
|
||||
* Call TvFeatureSupport to determine if the table to check supports the
|
||||
* timecapsule feature, and if not, use the ereport function to throw an
|
||||
* error.
|
||||
*
|
||||
* Param [IN] rte: Describes the tables that appear in the query.
|
||||
* Returns [OUT]: void
|
||||
*/
|
||||
void TvCheckVersionScan(RangeTblEntry *rte)
|
||||
{
|
||||
char *errstr = NULL;
|
||||
|
|
@ -178,6 +256,15 @@ void TvCheckVersionScan(RangeTblEntry *rte)
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* TvIsVersionScan() ---
|
||||
* Check if a scan is a "timecapsule" version scan.
|
||||
*
|
||||
* Param [IN] ss: A pointer to the ScanState structure, which contains
|
||||
* information about the scan.
|
||||
* Returns [OUT]: Return true if the scan is the "timecapsule" version scan;
|
||||
* Otherwise, return false.
|
||||
*/
|
||||
bool TvIsVersionScan(const ScanState *ss)
|
||||
{
|
||||
EState *estate = ss->ps.state;
|
||||
|
|
@ -188,7 +275,14 @@ bool TvIsVersionScan(const ScanState *ss)
|
|||
}
|
||||
|
||||
/*
|
||||
* Whether the plan contains version table scan.
|
||||
* TvIsVersionPlan() ---
|
||||
* Check if a schedule statement contains the "timecapsule" version
|
||||
* of the table.
|
||||
*
|
||||
* Param [IN] stmt: A pointer to the PlannedStmt structure that contains
|
||||
* information about the planned statement.
|
||||
* Returns [OUT]: Return true if the schedule statement contains the
|
||||
* "timecapsule" version of the table; Otherwise, return false.
|
||||
*/
|
||||
bool TvIsVersionPlan(const PlannedStmt *stmt)
|
||||
{
|
||||
|
|
@ -204,6 +298,30 @@ bool TvIsVersionPlan(const PlannedStmt *stmt)
|
|||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* TvTransformVersionExpr() ---
|
||||
* The function converts the given "timecapsule" version of the
|
||||
* expression (specified by tvver) to a specific type of expression
|
||||
* and checks if the expression contains a sublink.
|
||||
*
|
||||
* The function first calls the transformExpr function to convert the
|
||||
* "timecapsule" version expression. It then checks whether the converted
|
||||
* expression contains sublinks. The function casts the expression to a
|
||||
* specific type based on the "timecapsule" version type (specified by
|
||||
* tvtype). If the version type is a timestamp, it casts the expression
|
||||
* to the TIMESTAMPTZOID type; Otherwise, it casts the expression to
|
||||
* INT8OID type. Finally, the function calls assign_expr_collations
|
||||
* to assign the sorting of the expression and returns the converted
|
||||
* expression.
|
||||
*
|
||||
* Param [IN] pstate: A pointer to the ParseState structure, which
|
||||
* contains information about the parsing state.
|
||||
* Param [IN] tvtype: This is an enumeration value that represents
|
||||
* the "timecapsule" version type, which can be timestamp
|
||||
* (TV_VERSION_TIMESTAMP) or CSN (TV_VERSION_CSN).
|
||||
* Param [IN] tvver: Represents the "timecapsule" version expression.
|
||||
* Returns [OUT]: Represents the converted "timecapsule" version expression.
|
||||
*/
|
||||
Node *TvTransformVersionExpr(ParseState *pstate, TvVersionType tvtype, Node *tvver)
|
||||
{
|
||||
Node *verExpr = tvver;
|
||||
|
|
@ -225,7 +343,23 @@ Node *TvTransformVersionExpr(ParseState *pstate, TvVersionType tvtype, Node *tvv
|
|||
return verExpr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* TvEvalVerExpr() ---
|
||||
* The main purpose is to evaluate the "timecapsule" version expressions.
|
||||
*
|
||||
* First try to convert tvver to a constant. If successful, it will return
|
||||
* the constant directly. Otherwise, it creates a new ParseState, then calls
|
||||
* the TvTransformVersionExpr function to convert the "timecapsule" version
|
||||
* expression, and releases the parsing state. Next, the function calls the
|
||||
* evaluate_expr function to evaluate the converted expression and convert
|
||||
* the result to a constant. Double check that result is indeed Const.
|
||||
*
|
||||
* Param [IN] tvtype: This is an enumeration value that represents the
|
||||
* "timecapsule" version type, which can be timestamp (TV_VERSION_TIMESTAMP)
|
||||
* or CSN (TV_VERSION_CSN).
|
||||
* Param [IN] tvver: Represents the "timecapsule" version expression.
|
||||
* Returns [OUT]: Represents the evaluated "timecapsule" version expression.
|
||||
*/
|
||||
static Const *TvEvalVerExpr(TvVersionType tvtype, Node *tvver)
|
||||
{
|
||||
Const *result = (Const *)tvver;
|
||||
|
|
@ -394,6 +528,21 @@ static void TvFetchSnapCsn(int64 csn, Snapshot snap)
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* TvFetchSnap() ---
|
||||
* Get a snapshot of the "timecapsule" version.
|
||||
*
|
||||
* Call the function based on the "timecapsule" version type to get the
|
||||
* snapshot. If the version type is timestamp, it calls the TvFetchSnapTz
|
||||
* function; Otherwise, it calls the TvFetchSnapCsn function.
|
||||
*
|
||||
* Param [IN] type: Represents the "timecapsule" version type, which can
|
||||
* be timestamp (TV_VERSION_TIMESTAMP) or CSN (TV_VERSION_CSN).
|
||||
* Param [IN] value: Values that represent information about the "timecapsule"
|
||||
* version.
|
||||
* Returns [OUT]: Returns a pointer to the Snapshot. This snapshot represents
|
||||
* the obtained "timecapsule" version snapshot.
|
||||
*/
|
||||
static Snapshot TvFetchSnap(TvVersionType type, Const *value)
|
||||
{
|
||||
Snapshot snap = (Snapshot)palloc0(sizeof(SnapshotData));
|
||||
|
|
@ -409,6 +558,20 @@ static Snapshot TvFetchSnap(TvVersionType type, Const *value)
|
|||
return snap;
|
||||
}
|
||||
|
||||
/*
|
||||
* TvGetSnap() ---
|
||||
* The main purpose is to call the TvFetchSnap function to get
|
||||
* the "timecapsule" version snapshot of the specified table and
|
||||
* check for errors.
|
||||
*
|
||||
* Param [IN] relation: A pointer to the Relation structure, which
|
||||
* contains information about the table.
|
||||
* Param [IN] tvtype: Represents the "timecapsule" version type, which
|
||||
* can be timestamp (TV_VERSION_TIMESTAMP) or CSN (TV_VERSION_CSN).
|
||||
* Param [IN] tvver: Represents the "timecapsule" version expression.
|
||||
* Returns [OUT]: Returns a pointer to the Snapshot. This snapshot represents
|
||||
* the obtained "timecapsule" version snapshot.
|
||||
*/
|
||||
static Snapshot TvGetSnap(Relation relation, TvVersionType tvtype, Node *tvver)
|
||||
{
|
||||
Const *value;
|
||||
|
|
@ -426,6 +589,18 @@ static Snapshot TvGetSnap(Relation relation, TvVersionType tvtype, Node *tvver)
|
|||
return snap;
|
||||
}
|
||||
|
||||
/*
|
||||
* TvValidateRelDDL() ---
|
||||
* Check that the definition of the given table (specified by relid)
|
||||
* has changed.
|
||||
*
|
||||
* Param [IN] relid: It accepts a parameter called 'relid' of type
|
||||
* 'Oid'. 'Oid' is commonly used in database systems to represent
|
||||
* object identifiers.
|
||||
* Param [IN] snapcsn: This is a commit sequence number that represents
|
||||
* the value to be compared with the table's change sequence number.
|
||||
* Returns [OUT]: void
|
||||
*/
|
||||
static void TvValidateRelDDL(Oid relid, CommitSeqNo snapcsn)
|
||||
{
|
||||
Relation rel = RelationIdGetRelation(relid);
|
||||
|
|
@ -447,8 +622,18 @@ static void TvValidateRelDDL(Oid relid, CommitSeqNo snapcsn)
|
|||
}
|
||||
|
||||
/*
|
||||
* Choose user-specified snapshot if TimeCapsule clause exists, otherwise
|
||||
* estate->es_snapshot instead.
|
||||
* TvChooseScanSnap() ---
|
||||
* Select the appropriate snapshot to scan based on the given table,
|
||||
* scan, and scan states (specified by relation, scan, and ss).
|
||||
*
|
||||
* Param [IN] relation: A pointer to the Relation structure, which contains
|
||||
* information about the table.
|
||||
* Param [IN] scan: A pointer to the Scan structure that contains information
|
||||
* about the scan.
|
||||
* Param [IN] ss: A pointer to the ScanState structure that contains information
|
||||
* about the state of the scan.
|
||||
* Returns [OUT]: Returns a pointer to the Snapshot. This snapshot represents
|
||||
* the selected snapshot.
|
||||
*/
|
||||
Snapshot TvChooseScanSnap(Relation relation, Scan *scan, ScanState *ss)
|
||||
{
|
||||
|
|
@ -481,6 +666,22 @@ Snapshot TvChooseScanSnap(Relation relation, Scan *scan, ScanState *ss)
|
|||
return snap;
|
||||
}
|
||||
|
||||
/*
|
||||
* TvDeleteDelta() ---
|
||||
* Deletes all tuples for a given table (specified by relid).
|
||||
*
|
||||
* The function first opens the relationship (that is, the table) specified by
|
||||
* relid. It then scans the relationship and gets the next tuple. If a tuple
|
||||
* is found, it deletes the tuple. This process repeats until there are no
|
||||
* more tuples. Finally, the function ends the scan and closes the relationship.
|
||||
*
|
||||
* Param [IN] relid: It accepts a parameter called 'relid' of type 'Oid'.
|
||||
* 'Oid' is commonly used in database systems to represent object
|
||||
* identifiers.
|
||||
* Param [IN] snap: A pointer to the Snapshot structure that contains
|
||||
* information about the snapshot.
|
||||
* Returns [OUT]: void
|
||||
*/
|
||||
void TvDeleteDelta(Oid relid, Snapshot snap)
|
||||
{
|
||||
Relation rel;
|
||||
|
|
@ -500,6 +701,19 @@ void TvDeleteDelta(Oid relid, Snapshot snap)
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* TvUheapDeleteDeltaRel() ---
|
||||
* If partRel is NULL, all tuples of the rel table are deleted, otherwise all
|
||||
* tuples of the partRel table are deleted.
|
||||
*
|
||||
* Param [IN] rel & partRel: A pointer to the Relation structure, which contains
|
||||
* information about the table.
|
||||
* Param [IN] p: A pointer to a Partition structure that contains information about
|
||||
* the partition.
|
||||
* Param [IN] snap: A pointer to the Snapshot structure that contains
|
||||
* information about the snapshot.
|
||||
* Returns [OUT]: void
|
||||
*/
|
||||
void TvUheapDeleteDeltaRel(Relation rel, Relation partRel, Partition p, Snapshot snap)
|
||||
{
|
||||
TableScanDesc sd;
|
||||
|
|
@ -597,6 +811,18 @@ void TvUheapDeleteDeltaPart(Relation rel, Oid relid, Snapshot snap)
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* TvUheapDeleteDelta() ---
|
||||
* Deletes all tuples for a given table (specified by relid).This function
|
||||
* is used to process tables stored in Uheap mode.
|
||||
*
|
||||
* Param [IN] relid: It accepts a parameter called 'relid' of type 'Oid'.
|
||||
* 'Oid' is commonly used in database systems to represent object
|
||||
* identifiers.
|
||||
* Param [IN] snap: A pointer to the Snapshot structure that contains
|
||||
* information about the snapshot.
|
||||
* Returns [OUT]: void
|
||||
*/
|
||||
void TvUheapDeleteDelta(Oid relid, Snapshot snap)
|
||||
{
|
||||
Relation rel = heap_open(relid, NoLock);
|
||||
|
|
@ -609,6 +835,16 @@ void TvUheapDeleteDelta(Oid relid, Snapshot snap)
|
|||
heap_close(rel, NoLock);
|
||||
}
|
||||
|
||||
/*
|
||||
* TvFetchTuple() ---
|
||||
* Gets the next tuple pointed to by the table scan descriptor and returns
|
||||
* a copy of that tuple. If there are no more tuples, it will return NULL.
|
||||
*
|
||||
* Param [IN] arg: A pointer to any type, which will be converted to type
|
||||
* TableScanDesc and used to get the next tuple.
|
||||
* Returns [OUT]: A pointer of type TvFetchTupleHook. If the next tuple is obtained,
|
||||
* a copy of that tuple is returned. Otherwise, NULL is returned.
|
||||
*/
|
||||
typedef HeapTuple (*TvFetchTupleHook)(void *arg);
|
||||
static HeapTuple TvFetchTuple(void *arg)
|
||||
{
|
||||
|
|
@ -617,12 +853,43 @@ static HeapTuple TvFetchTuple(void *arg)
|
|||
return tup ? (HeapTuple)tableam_tops_copy_tuple(tup) : NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* TvUheapFetchTuple() ---
|
||||
* Gets the next tuple pointed to by the table scan descriptor and returns
|
||||
* a copy of that tuple. If there are no more tuples, it will return NULL.
|
||||
*/
|
||||
typedef UHeapTuple (*TvUheapFetchTupleHook)(void *arg);
|
||||
static UHeapTuple TvUheapFetchTuple(void *arg)
|
||||
{
|
||||
return (UHeapTuple)tableam_scan_getnexttuple((TableScanDesc)arg, ForwardScanDirection);
|
||||
}
|
||||
|
||||
/*
|
||||
* TvBatchInsert() ---
|
||||
* Batch inserts tuples into a table and updates any associated indexes.
|
||||
*
|
||||
* The function first switches to a short memory context to prevent memory leaks
|
||||
* when calling tableam_tuple_multi_insert. It then calls tableam_tuple_multi_insert
|
||||
* to insert all tuples in the buffer into the table. After that, it switches back
|
||||
* to its original memory context.
|
||||
* If the table has any indexes, the function traverses all inserted tuples and
|
||||
* calls ExecInsertIndexTuples on each tuple to update the index. Finally, it
|
||||
* frees the list returned by ExecInsertIndexTuples.
|
||||
*
|
||||
* Param [IN] rel: A pointer to the Relation structure, which contains information
|
||||
* about the table.
|
||||
* Param [IN] estate: An execution state object that contains all the information
|
||||
* needed to execute the query.
|
||||
* Param [IN] mycid: ID of the inserted column
|
||||
* Param [IN] hiOptions: An option flag that controls the behavior of an insert operation.
|
||||
* Param [IN] resultRelInfo: A result relationship information object that contains
|
||||
* information about the target relation(table).
|
||||
* Param [IN] myslot: Stores the tuple to be inserted.
|
||||
* Param [IN] bistate: Select different policies and manage batch insert operations.
|
||||
* Param [IN] nBufferedTuples: Represents the number of tuples in the buffer.
|
||||
* Param [IN] bufferedTuples: A pointer to a heap tuple array, indicating the tuple to be inserted.
|
||||
* Returns [OUT]:void
|
||||
*/
|
||||
static void TvBatchInsert(Relation rel, EState *estate, CommandId mycid,
|
||||
int hiOptions, ResultRelInfo *resultRelInfo,
|
||||
TupleTableSlot *myslot, BulkInsertState bistate,
|
||||
|
|
@ -666,6 +933,31 @@ static void TvBatchInsert(Relation rel, EState *estate, CommandId mycid,
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* TvInsertLostImpl
|
||||
* Batch inserts lost tuples into a table and updates any associated indexes.
|
||||
*
|
||||
* The function first creates an execution state object and a result relationship
|
||||
* information object, and opens all indexes of the table. It then initializes a
|
||||
* tuple table slot and allocates memory to store the tuple to be inserted.
|
||||
* Then enters an infinite loop, in which it uses the fetchTupleHook function to
|
||||
* fetch the next tuple and store it in the tuple table slot. If the tuple
|
||||
* satisfies all the constraints of the table, it adds the tuple to the buffer.
|
||||
* When the buffer is full or the total size of all tuples in the buffer exceeds
|
||||
* the preset maximum, the function calls TvBatchInsert to insert all tuples in
|
||||
* the buffer into the table and empty the buffer.
|
||||
* When there are no more tuples to fetch, the function exits the loop and inserts
|
||||
* any tuples remaining in the buffer into the table. Finally, the function releases
|
||||
* all allocated resources and returns.
|
||||
*
|
||||
* Param [IN] rel: A pointer to the Relation structure, which contains information
|
||||
* about the table.
|
||||
* Param [IN] snap: A pointer to the Snapshot structure that contains information
|
||||
* about the snapshot.
|
||||
* Param [IN] fetchTupleHook: A function pointer to a function that gets a tuple.
|
||||
* Param [IN] arg: It should be a pointer to a bunch of tuples.
|
||||
* Returns [OUT]: void
|
||||
*/
|
||||
const int MAX_BUFFERED_TUPLES_TCAP = 1000;
|
||||
const int MAX_BUFFERED_TUPLES_NUM_TCAP = 65535;
|
||||
static void TvInsertLostImpl(Relation rel, Snapshot snap, TvFetchTupleHook fetchTupleHook, void *arg)
|
||||
|
|
@ -773,6 +1065,10 @@ static void TvInsertLostImpl(Relation rel, Snapshot snap, TvFetchTupleHook fetch
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* TvUheapInsertLostImpl() ---
|
||||
* As for TvInsertLostImpl, please read the notes for TvInsertLostImpl.
|
||||
*/
|
||||
static void TvUheapInsertLostImpl(Relation rel, Relation partRel, Partition p,
|
||||
Snapshot snap, TvUheapFetchTupleHook fetchTupleHook, void *arg)
|
||||
{
|
||||
|
|
@ -847,6 +1143,21 @@ static void TvUheapInsertLostImpl(Relation rel, Relation partRel, Partition p,
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* TvInsertLost()---
|
||||
* Call the TvInsertLostImpl function to insert the "missing" tuple into a table.
|
||||
*
|
||||
* First opens the table specified by relid and starts a table scan. It then calls
|
||||
* the TvInsertLostImpl function to get and insert the "missing" tuple. Finally, it
|
||||
* ends the table scan and closes the table.
|
||||
*
|
||||
* Param [IN] relid: It accepts a parameter called 'relid' of type 'Oid'.
|
||||
* 'Oid' is commonly used in database systems to represent object
|
||||
* identifiers.
|
||||
* Param [IN] snap: A pointer to the Snapshot structure that contains
|
||||
* information about the snapshot.
|
||||
* Returns [OUT]: void
|
||||
*/
|
||||
void TvInsertLost(Oid relid, Snapshot snap)
|
||||
{
|
||||
Relation rel;
|
||||
|
|
@ -865,6 +1176,23 @@ void TvInsertLost(Oid relid, Snapshot snap)
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* TvUheapInsertLostRel() ---
|
||||
* Inserts a lost tuple into a table(rel) or a part table(partRel).
|
||||
*
|
||||
* First check if partRel is NULL. If so, it starts scanning the tables specified
|
||||
* by relid; Otherwise, it starts scanning the table parts specified by partRel.
|
||||
* It then calls the TvUheapInsertLostImpl function to get and insert the "lost"
|
||||
* tuple.
|
||||
*
|
||||
* Param [IN] rel & partRel: A pointer to the Relation structure, which contains
|
||||
* information about the table.
|
||||
* Param [IN] p: A pointer to a Partition structure that contains information about
|
||||
* the partition.
|
||||
* Param [IN] snap: A pointer to the Snapshot structure that contains information
|
||||
* about the snapshot.
|
||||
* Returns [OUT]: void
|
||||
*/
|
||||
void TvUheapInsertLostRel(Relation rel, Relation partRel, Partition p, Snapshot snap)
|
||||
{
|
||||
TableScanDesc sd;
|
||||
|
|
@ -879,6 +1207,29 @@ void TvUheapInsertLostRel(Relation rel, Relation partRel, Partition p, Snapshot
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* TvUheapInsertLostPart() ---
|
||||
* Insert the lost tuple into the partition of a table.
|
||||
*
|
||||
* The function first gets all the partitions of the table specified by relid.
|
||||
* It then traverses all partitions and does the following for each partition:
|
||||
* 1. Opens the partition and gets its relational objects.
|
||||
* 2. Check whether the partition has subpartitions. If so, it gets all the
|
||||
* subpartitions and calls the TvUheapInsertLostRel function on each subpartition
|
||||
* to insert the "lost" tuple. It then closes the subpartition and frees the
|
||||
* relational object.
|
||||
* 3. If the partition has no subpartitions, it directly calls the TvUheapInsertLostRel
|
||||
* function to insert the "lost" tuple.
|
||||
* 4. Finally, it closes the partition and frees the relational object.
|
||||
*
|
||||
* Param [IN] rel: A pointer to the Relation structure, which contains information
|
||||
* about the table.
|
||||
* Param [IN] relid: It accepts a parameter called 'relid' of type 'Oid'.'Oid'
|
||||
* is commonly used in database systems to represent object identifiers.
|
||||
* Param [IN] snap: A pointer to the Snapshot structure that contains information
|
||||
* about the snapshot.
|
||||
* Returns [OUT]: void
|
||||
*/
|
||||
void TvUheapInsertLostPart(Relation rel, Oid relid, Snapshot snap)
|
||||
{
|
||||
List* partTupleList = NIL;
|
||||
|
|
@ -934,6 +1285,14 @@ void TvUheapInsertLost(Oid relid, Snapshot snap)
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* TvCheckVersionRestore() ---
|
||||
* Check whether the version of the table is allowed to be restored,
|
||||
* and check whether the current user has permission to do so.
|
||||
*
|
||||
* Param [IN] rel: Represents the version of the table to be recovered.
|
||||
* Returns [OUT]: void
|
||||
*/
|
||||
static void TvCheckVersionRestore(Relation rel)
|
||||
{
|
||||
char *errstr = NULL;
|
||||
|
|
@ -957,6 +1316,13 @@ static void TvCheckVersionRestore(Relation rel)
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* TvRestoreVersion() ---
|
||||
* Restores a version of a table.
|
||||
*
|
||||
* Param [IN] stmt: Contains information and version information about the table to be recovered.
|
||||
* Retuens [OUT]: void
|
||||
*/
|
||||
void TvRestoreVersion(TimeCapsuleStmt *stmt)
|
||||
{
|
||||
Relation rel;
|
||||
|
|
|
|||
|
|
@ -46,10 +46,17 @@ typedef struct SeqScanAccessor {
|
|||
uint32 sa_prefetch_trigger; /* the prefetch-trigger distance bewteen last prefetched buffer and currently accessed buffer */
|
||||
} SeqScanAccessor;
|
||||
|
||||
/* -----------------------------------------------------------------
|
||||
* RangeScanInRedis
|
||||
* -----------------------------------------------------------------
|
||||
* Note:
|
||||
* This structure is used to store some information in redistrition.
|
||||
* -----------------------------------------------------------------
|
||||
*/
|
||||
typedef struct RangeScanInRedis{
|
||||
uint8 isRangeScanInRedis;
|
||||
uint8 sliceTotal;
|
||||
uint8 sliceIndex;
|
||||
uint8 isRangeScanInRedis; /* if it is a range scan in Redistribution */
|
||||
uint8 sliceTotal; /* total number of slices */
|
||||
uint8 sliceIndex; /* current slice index */
|
||||
} RangeScanInRedis;
|
||||
|
||||
/*
|
||||
|
|
@ -98,8 +105,8 @@ typedef struct TableScanDescData
|
|||
TupleTableSlot *slot; /* For begin scan of CopyTo */
|
||||
|
||||
/* variables for batch mode scan */
|
||||
int rs_ctupRows;
|
||||
int rs_maxScanRows;
|
||||
int rs_ctupRows; /* current line number scanned */
|
||||
int rs_maxScanRows; /* maximum number of rows that can be scanned */
|
||||
} TableScanDescData;
|
||||
|
||||
/* struct definition appears in relscan.h */
|
||||
|
|
|
|||
|
|
@ -1668,8 +1668,8 @@ typedef struct ScanState {
|
|||
List* partitions; /* list of Partition */
|
||||
List* subpartitions; /* list of SubPartition */
|
||||
LOCKMODE lockMode;
|
||||
List* runTimeParamPredicates;
|
||||
bool runTimePredicatesReady;
|
||||
List* runTimeParamPredicates; /* list of predicates to be filtered*/
|
||||
bool runTimePredicatesReady; /* whether the filter predicate is ready to run*/
|
||||
bool is_scan_end; /* @hdfs Mark whether iterator is over or not, if the scan uses informational constraint. */
|
||||
SeqScanAccessor* ss_scanaccessor; /* prefetch related */
|
||||
int part_id;
|
||||
|
|
|
|||
|
|
@ -41,9 +41,11 @@ typedef struct MemoryContextMethods {
|
|||
void (*init)(MemoryContext context);
|
||||
void (*reset)(MemoryContext context);
|
||||
void (*delete_context)(MemoryContext context);
|
||||
/* Gets the MemoryContext block size*/
|
||||
Size (*get_chunk_space)(MemoryContext context, void* pointer);
|
||||
bool (*is_empty)(MemoryContext context);
|
||||
void (*stats)(MemoryContext context, int level);
|
||||
/* MemoryContext exception check*/
|
||||
#ifdef MEMORY_CONTEXT_CHECKING
|
||||
void (*check)(MemoryContext context);
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Reference in New Issue