openGauss-server/src/gausskernel/runtime/executor/execCurrent.cpp

462 lines
20 KiB
C++

/***
* @Author: 王语翀
* @Team: 兰心开源
*/
/* -------------------------------------------------------------------------
*
* execCurrent.c
* executor support for WHERE CURRENT OF cursor
*
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* src/backend/executor/execCurrent.c
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include "access/sysattr.h"
#include "access/tableam.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/portal.h"
#include "utils/rel.h"
#include "utils/rel_gs.h"
#ifdef PGXC
#include "pgxc/execRemote.h"
#endif
static char* fetch_cursor_param_value(ExprContext *econtext, int paramId);
#ifndef PGXC
static ScanState* search_plan_tree(PlanState *node, Oid table_oid);
#endif
/*
* execCurrentOf
*
* Given a CURRENT OF expression and the OID of a table, determine which row
* of the table is currently being scanned by the cursor named by CURRENT OF,
* and return the row's TID into *current_tid.
*
* Returns TRUE if a row was identified. Returns FALSE if the cursor is valid
* for the table but is not currently scanning a row of the table (this is a
* legal situation in inheritance cases). Raises error if cursor is not a
* valid updatable scan of the specified table.
*/
/*The segment code defines a function named execCurrentOf, which is used to execute the CURRENT OF expression in SQL query.
The CURRENT OF expression is a part of SQL/PSM (Persistent Storage Module),
which is used to implement sensitive operations on a cursor.
The function execCurrentOf receives five parameters:
1. cexpr: a pointer to the CurrentOfExpr structure, which contains the information of the CURRENT OF expression.
2. econtext: a pointer to ExprContext structure, which contains the context information of expression execution.
3. Relationship: A pointer to the relationship structure, which represents a relationship (i.e. a table) in the database.
4. current_tid: a pointer to the ItemPointer structure, which represents the current transaction ID.
5. partitionOfCursor_tid: A pointer to the RelationPtr structure, which represents the partition of the cursor.
The function first obtains the name of the cursor according to cexpr, and
then finds the corresponding Portal according to the name.
If a valid Portal cannot be found, an error is reported. Then, the function checks
the query description (query_desc) corresponding to the Portal.
An error is also reported if the query description does not exist
or the status of the query description is invalid.
Then, the function decides which strategy to execute according to the row marks in the query description.
If there is a line mark, use FOR UPDATE/SHARE; Otherwise, use a FOR-UPDATE method.
It defines a variable named `erm` with an initial value of NULL. Then it traverses ` query _ desc-> estate-> es _ row marks`,
which is a list of all the rowmarks in the cursor query. During traversal, it
checks whether each row tag needs a row share lock, and if not, it ignores the row tag.
For the row tag that needs a row sharing lock, the code checks whether the table associated with the row tag is
the target table (that is, the OID returned by the RelationGetRelid' of `thiserm-> relation' is equal to table_oid').
If it is, and there is already a row tag associated with the target table, it will report an error because
the cursor cannot have more than one FOR UPDATE/SHARE reference to the same table.
After the traversal is completed, if the row tag associated with the target table is not found, it will report an error,
because the cursor must have a FOR UPDATE/SHARE reference to the target table.
Next, the code checks whether the cursor currently has a result row.
If not, it will report an error, because in the SQL specification, this is wrong.
Finally, if there is a valid TID (transaction ID) of the current scan, it will set' current_tid' and check whether
the relationship is partitioned. If the relationship is partitioned, it will set `partition of cursor _ tid' to NULL.
Then return true, indicating that the related TID has been found. If a valid TID is not found, it will return false,
indicating that this table has not generated the current row of the cursor, and other inherited
sub-tables may have generated the current row of the cursor.
Some variables are defined, including a pointer named scanstate', a boolean variable` lisnull',
an Oid variable` tuple_tableoid' and an ItemPointer variable` tuple_tid'.
Then, it searches the search_plan_tree by calling the `search _ plan _ tree` function to find the scan node
associated with the given table OID. If the scan node is not found,
or the scan node is overwritten by the aggregation operation, it will report an error.
Next, the code checks whether the cursor currently has a result row. If not,
it will report an error, because in the SQL specification, this is wrong.
Then, if the current scan tuple in the scan state is NULL, it will return false.
Finally, the code uses the slot_getattr function to get the table OID and transaction ID of
the tuple and check whether they are valid. If the relationship is partitioned, it will also check
whether the table OID is the same as the parent table OID of the partition.*/
bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relation, ItemPointer current_tid,
RelationPtr partitionOfCursor_tid)
{
char *cursor_name = NULL;
Portal portal;
QueryDesc *query_desc = NULL;
Oid table_oid = RelationGetRelid(relation);
/* Get the cursor name --- may have to look up a parameter reference */
if (cexpr->cursor_name) {
cursor_name = cexpr->cursor_name;
} else {
cursor_name = fetch_cursor_param_value(econtext, cexpr->cursor_param);
}
/* Find the cursor's portal */
portal = GetPortalByName(cursor_name);
if (!PortalIsValid(portal)) {
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_CURSOR),
errmsg("cursor \"%s\" does not exist when executing Current Of Expr.", cursor_name)));
}
/*
* We have to watch out for non-SELECT queries as well as held cursors,
* both of which may have null query_desc.
*/
if (portal->strategy != PORTAL_ONE_SELECT) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg("cursor \"%s\" is not a SELECT query", cursor_name)));
}
query_desc = PortalGetQueryDesc(portal);
if (query_desc == NULL || query_desc->estate == NULL) {
ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE),
errmsg("cursor \"%s\" is held from a previous transaction", cursor_name)));
}
/*
* We have two different strategies depending on whether the cursor uses
* FOR UPDATE/SHARE or not. The reason for supporting both is that the
* FOR UPDATE code is able to identify a target table in many cases where
* the other code can't, while the non-FOR-UPDATE case allows use of WHERE
* CURRENT OF with an insensitive cursor.
*/
if (query_desc->estate->es_rowMarks) {
ExecRowMark *erm = NULL;
ListCell *lc = NULL;
/*
* Here, the query must have exactly one FOR UPDATE/SHARE reference to
* the target table, and we dig the ctid info out of that.
*/
erm = NULL;
foreach (lc, query_desc->estate->es_rowMarks) {
ExecRowMark *thiserm = (ExecRowMark *)lfirst(lc);
if (!RowMarkRequiresRowShareLock(thiserm->markType)) {
continue; /* ignore non-FOR UPDATE/SHARE items */
}
if (RelationGetRelid(thiserm->relation) == table_oid) {
if (erm != NULL) {
ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE),
errmsg("cursor \"%s\" has multiple FOR UPDATE/SHARE references to table \"%s\"", cursor_name,
RelationGetRelationName(relation))));
}
erm = thiserm;
}
}
if (erm == NULL) {
ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE),
errmsg("cursor \"%s\" does not have a FOR UPDATE/SHARE reference to table \"%s\"", cursor_name,
RelationGetRelationName(relation))));
}
/*
* The cursor must have a current result row: per the SQL spec, it's
* an error if not.
*/
if (portal->atStart || portal->atEnd) {
ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE),
errmsg("cursor \"%s\" is not positioned on a row when the cursor uses for UPDATE/SHARE", cursor_name)));
}
/* Return the currently scanned TID, if there is one */
if (ItemPointerIsValid(&(erm->curCtid))) {
*current_tid = erm->curCtid;
if (RELATION_IS_PARTITIONED(relation)) {
*partitionOfCursor_tid = NULL;
}
return true;
}
/*
* This table didn't produce the cursor's current row; some other
* inheritance child of the same parent must have. Signal caller to
* do nothing on this table.
*/
return false;
} else {
ScanState *scanstate = NULL;
bool lisnull = false;
Oid tuple_tableoid PG_USED_FOR_ASSERTS_ONLY;
ItemPointer tuple_tid;
/*
* Without FOR UPDATE, we dig through the cursor's plan to find the
* scan node. Fail if it's not there or buried underneath
* aggregation.
*/
scanstate = search_plan_tree(query_desc->planstate, table_oid);
if (scanstate == NULL) {
ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE),
errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"", cursor_name,
RelationGetRelationName(relation))));
}
/*
* The cursor must have a current result row: per the SQL spec, it's
* an error if not. We test this at the top level, rather than at the
* scan node level, because in inheritance cases any one table scan
* could easily not be on a row. We want to return false, not raise
* error, if the passed-in table OID is for one of the inactive scans.
*/
if (portal->atStart || portal->atEnd) {
ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg(
"cursor \"%s\" is not positioned on a row when the cursor doesn't use for UPDATE/SHARE", cursor_name)));
}
/* Now OK to return false if we found an inactive scan */
if (TupIsNull(scanstate->ss_ScanTupleSlot)) {
return false;
}
/* Use slot_getattr to catch any possible mistakes */
tuple_tableoid = DatumGetObjectId(tableam_tslot_getattr(scanstate->ss_ScanTupleSlot, TableOidAttributeNumber, &lisnull));
Assert(!lisnull);
tuple_tid = (ItemPointer)DatumGetPointer(
tableam_tslot_getattr(scanstate->ss_ScanTupleSlot, SelfItemPointerAttributeNumber, &lisnull));
Assert(!lisnull);
if (RELATION_IS_PARTITIONED(relation)) {
Assert(tuple_tableoid == RelationGetRelid(scanstate->ss_currentPartition));
*partitionOfCursor_tid = scanstate->ss_currentPartition;
} else {
Assert(tuple_tableoid == table_oid);
}
*current_tid = *tuple_tid;
return true;
}
}
/*
* fetch_cursor_param_value
*
* Fetch the string value of a param, verifying it is of type REFCURSOR.
*/
/*This code defines a function `fetch _ cursor _ param _ value`, which is used to get the specified
parameter value, especially when the parameter is of the reference cursor type.
The input parameters of the function include a pointer to an ExprContext structure and an integer paramId.
The ExprContext' structure contains the execution context of the expression, which may contain
some parameter information. ParamId' is the ID of the parameter to get.
The function first checks whether there is parameter information and whether the parameter ID is within the valid range.
Then, it locates the specific parameter and checks its type. If the parameter type is dynamic (that is, its type identifier is invalid)
and there is a parameter obtaining function, it will call this function to obtain the value of the parameter.
If the parameter type is valid and not null, the function will check further. If the parameter type is not a reference refcursor,
it will report an error because the function only deals with this type. If the parameter type is a reference cursor,
the function will convert its value to a C string and return this string.
If the value of the parameter is not found during the execution of the function, it will report an error and return NULL.*/
static char *fetch_cursor_param_value(ExprContext *econtext, int paramId)
{
ParamListInfo paramInfo = econtext->ecxt_param_list_info;
if (paramInfo && paramId > 0 && paramId <= paramInfo->numParams) {
ParamExternData *prm = &paramInfo->params[paramId - 1];
/* give hook a chance in case parameter is dynamic */
if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL) {
(*paramInfo->paramFetch)(paramInfo, paramId);
}
if (OidIsValid(prm->ptype) && !prm->isnull) {
/* safety check in case hook did something unexpected */
if (prm->ptype != REFCURSOROID) {
ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)", paramId,
format_type_be(prm->ptype), format_type_be(REFCURSOROID))));
}
/* We know that refcursor uses text's I/O routines */
return TextDatumGetCString(prm->value);
}
}
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("no value found for parameter %d", paramId)));
return NULL;
}
/*
* search_plan_tree
*
* Search through a PlanState tree for a scan node on the specified table.
* Return NULL if not found or multiple candidates.
*/
/*t searches the PlanState tree for scan nodes on the specified table.
In PostgreSQL, the PlanState tree is a data structure representing the query execution plan.
The function `search _ plan _ tree` receives two parameters: a pointer` node of PlanState and
a ` table _ Oid` of oid type. The function starts searching from the given node
and finds the scanning node that matches the OID of the specified table.
In the code, use the switch statement to judge the type of the node. For each scan node type that can be processed
(for example, sequential scan, index scan, index only scan, bitmap heap scan and TID scan), the code checks whether the ID
of the current relationship (that is, the scanned table) matches the given table OID.
If there is a match, the function returns a pointer to the scan node.
For the `t _ remotequerystate` node, the code will return the scanning status of the node.
For the `t _ extensibleplanstate' node, the code will check whether the ID of the current relationship matches
the given table OID, and return the scanning status at the time of matching.
For the `t _ appendstate` node, the code will iterate through all the attached plans and recursively call the `search _ plan _ tree` function.
If multiple matching scan nodes are found in the attached schedule, the function will return NULL.
-`T_AppendState' and `t _ mergeappendState': Both node types represent a method of combining multiple subquery results into one result.
The code will traverse each subquery and recursively call the `search _ plan _ tree` function for each subquery.
If a matching scanning node is found, and no matching node has been found before, the matching node is assigned to result.
If multiple matching nodes are found, the function will return NULL.
-`t _ resultstate`, `t _ limitstate`, `t _ partiteratorstate`, and `t _ materialstate` (only exists in PGXC): These node types can be
traversed directly because they always return the current line of their input.
-`T_SubqueryScanState: This node type represents the scanning of the subquery,
and the code will return the scanning node in the subquery.
-Default: If the node is not of any of the above types,
the code will assume that it cannot traverse through the node, so it will return NULL.
The main purpose of this function is to find the scanning node corresponding to a specific table in the query execution plan.
This is very useful for understanding and tracking query execution, especially
when it is necessary to understand and debug query performance problems.
Generally speaking, this function is used to find the scan node corresponding to the specified table in the query execution plan.*/
#ifdef PGXC
ScanState* search_plan_tree(PlanState* node, Oid table_oid)
#else
static ScanState* search_plan_tree(PlanState* node, Oid table_oid)
#endif
{
if (node == NULL) {
return NULL;
}
switch (nodeTag(node)) {
#ifdef PGXC
case T_RemoteQueryState: {
RemoteQueryState *rqs = (RemoteQueryState *)node;
ScanState *sstate = &(rqs->ss);
return sstate;
}
#endif
/*
* scan nodes can all be treated alike
*/
case T_SeqScanState:
case T_IndexScanState:
case T_IndexOnlyScanState:
case T_BitmapHeapScanState:
case T_TidScanState: {
ScanState *sstate = (ScanState *)node;
if (RelationGetRelid(sstate->ss_currentRelation) == table_oid) {
return sstate;
}
break;
}
case T_ExtensiblePlanState: {
ScanState *sstate = (ScanState *)node;
ScanState *result = NULL;
if (RelationGetRelid(sstate->ss_currentRelation) == table_oid) {
result = sstate;
}
return result;
}
/*
* For Append, we must look through the members; watch out for
* multiple matches (possible if it was from UNION ALL)
*/
case T_AppendState: {
AppendState *astate = (AppendState *)node;
ScanState *result = NULL;
int i;
for (i = 0; i < astate->as_nplans; i++) {
ScanState *elem = search_plan_tree(astate->appendplans[i], table_oid);
if (elem == NULL)
continue;
if (result != NULL)
return NULL; /* multiple matches */
result = elem;
}
return result;
}
/*
* Similarly for MergeAppend
*/
case T_MergeAppendState: {
MergeAppendState *mstate = (MergeAppendState *)node;
ScanState *result = NULL;
int i;
for (i = 0; i < mstate->ms_nplans; i++) {
ScanState *elem = search_plan_tree(mstate->mergeplans[i], table_oid);
if (elem == NULL) {
continue;
}
if (result != NULL) {
return NULL; /* multiple matches */
}
result = elem;
}
return result;
}
/*
* Result and Limit can be descended through (these are safe
* because they always return their input's current row)
*/
#ifdef PGXC
case T_MaterialState:
#endif
case T_ResultState:
case T_LimitState:
case T_PartIteratorState:
return search_plan_tree(node->lefttree, table_oid);
/*
* SubqueryScan too, but it keeps the child in a different place
*/
case T_SubqueryScanState:
return search_plan_tree(((SubqueryScanState *)node)->subplan, table_oid);
default:
/* Otherwise, assume we can't descend through it */
break;
}
return NULL;
}