forked from huawei/openGauss-server
462 lines
20 KiB
C++
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 = ¶mInfo->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;
|
|
}
|