Files
openGauss-server/src/common/backend/parser/analyze.cpp
2023-05-27 16:47:37 +08:00

5648 lines
209 KiB
C++

/* -------------------------------------------------------------------------
*
* analyze.cpp
* transform the raw parse tree into a query tree
*
* For optimizable statements, we are careful to obtain a suitable lock on
* each referenced table, and other modules of the backend preserve or
* re-obtain these locks before depending on the results. It is therefore
* okay to do significant semantic analysis of these statements. For
* utility commands, no locks are obtained here (and if they were, we could
* not be sure we'd still have them at execution). Hence the general rule
* for utility commands is to just dump them into a Query node untransformed.
* DECLARE CURSOR, EXPLAIN, and CREATE TABLE AS are exceptions because they
* contain optimizable statements, which we should transform.
*
*
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
* Portions Copyright (c) 2021, openGauss Contributors
*
* src/common/backend/parser/analyze.cpp
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include "access/sysattr.h"
#ifdef PGXC
#include "catalog/pg_inherits.h"
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_proc.h"
#include "catalog/pgxc_class.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "utils/fmgroids.h"
#include "utils/snapmgr.h"
#endif
#include "catalog/pg_auth_history.h"
#include "catalog/pg_type.h"
#include "executor/node/nodeModifyTable.h"
#include "foreign/foreign.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
#include "parser/parse_agg.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_cte.h"
#include "parser/parse_hint.h"
#include "parser/parse_merge.h"
#include "parser/parse_oper.h"
#include "parser/parse_param.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#include "parser/parse_type.h"
#include "parser/parse_expr.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "rewrite/rewriteRlsPolicy.h"
#include "rewrite/rewriteHandler.h"
#ifdef PGXC
#include "miscadmin.h"
#include "pgxc/pgxc.h"
#include "pgxc/pgxcnode.h"
#include "access/gtm.h"
#include "utils/distribute_test.h"
#include "utils/lsyscache.h"
#include "optimizer/pgxcplan.h"
#include "tcop/tcopprot.h"
#include "nodes/nodes.h"
#include "pgxc/poolmgr.h"
#include "catalog/pgxc_node.h"
#include "access/xact.h"
#include "utils/distribute_test.h"
#include "tcop/utility.h"
#endif
#include "utils/rel.h"
#include "utils/rel_gs.h"
#include "utils/acl.h"
#include "commands/explain.h"
#include "commands/sec_rls_cmds.h"
#include "streaming/streaming_catalog.h"
#include "instruments/instr_unique_sql.h"
#include "streaming/init.h"
#include "db4ai/aifuncs.h"
#include "db4ai/create_model.h"
#include "db4ai/hyperparameter_validation.h"
#ifdef ENABLE_MOT
#include "storage/mot/jit_exec.h"
#endif
#ifndef ENABLE_MULTIPLE_NODES
#include "optimizer/clauses.h"
#endif
/* Hook for plugins to get control at end of parse analysis */
THR_LOCAL post_parse_analyze_hook_type post_parse_analyze_hook = NULL;
static const int MILLISECONDS_PER_SECONDS = 1000;
static Query* transformDeleteStmt(ParseState* pstate, DeleteStmt* stmt);
static Query* transformInsertStmt(ParseState* pstate, InsertStmt* stmt);
static void checkUpsertTargetlist(Relation targetTable, List* updateTlist);
static UpsertExpr* transformUpsertClause(ParseState* pstate, UpsertClause* upsertClause, List* resultRelations);
static int count_rowexpr_columns(ParseState* pstate, Node* expr);
static void transformVariableSetStmt(ParseState* pstate, VariableSetStmt* stmt);
static Query* transformVariableMutiSetStmt(ParseState* pstate, VariableMultiSetStmt* muti_stmt);
static Query* transformSelectStmt(
ParseState* pstate, SelectStmt* stmt, bool isFirstNode = true, bool isCreateView = false);
static Query* transformValuesClause(ParseState* pstate, SelectStmt* stmt);
static Query* transformSetOperationStmt(ParseState* pstate, SelectStmt* stmt);
static Node* transformSetOperationTree(ParseState* pstate, SelectStmt* stmt, bool isTopLevel, List** targetlist);
static void determineRecursiveColTypes(ParseState* pstate, Node* larg, List* nrtargetlist);
static Query* transformUpdateStmt(ParseState* pstate, UpdateStmt* stmt);
static List* transformUpdateTargetList(ParseState* pstate, List* qryTlist, List* origTlist, List* resultRelations);
static List* transformReturningList(ParseState* pstate, List* returningList);
static Query* transformDeclareCursorStmt(ParseState* pstate, DeclareCursorStmt* stmt);
static Query* transformExplainStmt(ParseState* pstate, ExplainStmt* stmt);
static Query* transformCreateTableAsStmt(ParseState* pstate, CreateTableAsStmt* stmt);
static void CheckDeleteRelation(Relation targetrel);
static void CheckUpdateRelation(Relation targetrel);
static void transformVariableSetValueStmt(ParseState* pstate, VariableSetStmt* stmt);
#ifdef PGXC
static Query* transformExecDirectStmt(ParseState* pstate, ExecDirectStmt* stmt);
static bool IsExecDirectUtilityStmt(const Node* node);
static bool is_relation_child(RangeTblEntry* child_rte, List* rtable);
static bool is_rel_child_of_rel(RangeTblEntry* child_rte, RangeTblEntry* parent_rte);
#endif
static List* remove_update_redundant_relation(List* resultRelations, List* target_rangetblentry);
static void transformLockingClause(ParseState* pstate, Query* qry, LockingClause* lc, bool pushedDown);
static bool IsFindHDFSForeignTbl(Query* qry);
static bool checkForeignTableExist(List* rte_table_list);
static void set_subquery_is_under_insert(ParseState* subParseState);
static void set_ancestor_ps_contain_foreigntbl(ParseState* subParseState);
static bool include_groupingset(Node* groupClause);
static void transformGroupConstToColumn(ParseState* pstate, Node* groupClause, List* targetList);
static bool checkAllowedTableCombination(ParseState* pstate);
#ifdef ENABLE_MULTIPLE_NODES
static bool ContainSubLinkWalker(Node* node, void* context);
static bool ContainSubLink(Node* clause);
#endif /* ENABLE_MULTIPLE_NODES */
#ifndef ENABLE_MULTIPLE_NODES
static const char* NOKEYUPDATE_KEYSHARE_ERRMSG = "/NO KEY UPDATE/KEY SHARE";
#else
static const char* NOKEYUPDATE_KEYSHARE_ERRMSG = "";
#endif
/*
* parse_analyze
* Analyze a raw parse tree and transform it to Query form.
*
* Optionally, information about $n parameter types can be supplied.
* References to $n indexes not defined by paramTypes[] are disallowed.
*
* The result is a Query node. Optimizable statements require considerable
* transformation, while utility-type statements are simply hung off
* a dummy CMD_UTILITY Query node.
*/
Query* parse_analyze(
Node* parseTree, const char* sourceText, Oid* paramTypes, int numParams, bool isFirstNode, bool isCreateView)
{
ParseState* pstate = make_parsestate(NULL);
Query* query = NULL;
/* required as of 8.4 */
AssertEreport(sourceText != NULL, MOD_OPT, "para cannot be NULL");
pstate->p_sourcetext = sourceText;
if (numParams > 0) {
parse_fixed_parameters(pstate, paramTypes, numParams);
}
PUSH_SKIP_UNIQUE_SQL_HOOK();
query = transformTopLevelStmt(pstate, parseTree, isFirstNode, isCreateView);
POP_SKIP_UNIQUE_SQL_HOOK();
/* it's unsafe to deal with plugins hooks as dynamic lib may be released */
if (post_parse_analyze_hook && !(g_instance.status > NoShutdown)) {
(*post_parse_analyze_hook)(pstate, query);
}
pfree_ext(pstate->p_ref_hook_state);
pstate->rightRefState = nullptr;
free_parsestate(pstate);
/* For plpy CTAS query. CTAS is a recursive call. CREATE query is the first rewrited.
* thd 2nd rewrited query is INSERT SELECT. Without this attribute, DB will have
* an error that has no idea about $x when INSERT SELECT query is analyzed.
*/
query->fixed_paramTypes = paramTypes;
query->fixed_numParams = numParams;
return query;
}
/*
* parse_analyze_varparams
*
* This variant is used when it's okay to deduce information about $n
* symbol datatypes from context. The passed-in paramTypes[] array can
* be modified or enlarged (via repalloc).
*/
Query* parse_analyze_varparams(Node* parseTree, const char* sourceText, Oid** paramTypes, int* numParams)
{
ParseState* pstate = make_parsestate(NULL);
Query* query = NULL;
/* required as of 8.4 */
AssertEreport(sourceText != NULL, MOD_OPT, "para cannot be NULL");
pstate->p_sourcetext = sourceText;
parse_variable_parameters(pstate, paramTypes, numParams);
query = transformTopLevelStmt(pstate, parseTree);
/* make sure all is well with parameter types */
check_variable_parameters(pstate, query);
/* it's unsafe to deal with plugins hooks as dynamic lib may be released */
if (post_parse_analyze_hook && !(g_instance.status > NoShutdown)) {
(*post_parse_analyze_hook)(pstate, query);
}
pfree_ext(pstate->p_ref_hook_state);
free_parsestate(pstate);
/* For plpy CTAS query. CTAS is a recursive call. CREATE query is the first rewrited.
* thd 2nd rewrited query is INSERT SELECT. Without this attribute, DB will have
* an error that has no idea about $x when INSERT SELECT query is analyzed.
*/
Assert(PointerIsValid(query));
query->fixed_paramTypes = *paramTypes;
query->fixed_numParams = *numParams;
return query;
}
/*
* parse_sub_analyze
* Entry point for recursively analyzing a sub-statement.
*/
Query* parse_sub_analyze(Node* parseTree, ParseState* parentParseState, CommonTableExpr* parentCTE,
bool locked_from_parent, bool resolve_unknowns)
{
ParseState* pstate = make_parsestate(parentParseState);
Query* query = NULL;
pstate->p_parent_cte = parentCTE;
pstate->p_locked_from_parent = locked_from_parent;
pstate->p_resolve_unknowns = resolve_unknowns;
if (u_sess->attr.attr_sql.td_compatible_truncation && u_sess->attr.attr_sql.sql_compatibility == C_FORMAT)
set_subquery_is_under_insert(pstate); /* Set p_is_in_insert for parse state.*/
query = transformStmt(pstate, parseTree);
free_parsestate(pstate);
return query;
}
Node* parse_into_claues(Node* parseTree, IntoClause* intoClause)
{
if (intoClause->userVarList) {
UserSetElem* uset = makeNode(UserSetElem);
uset->name = intoClause->userVarList;
SubLink* sl = makeNode(SubLink);
sl->subLinkType = EXPR_SUBLINK;
sl->testexpr = NULL;
sl->operName = NIL;
sl->subselect = parseTree;
sl->location = -1;
SelectIntoVarList *sis = makeNode(SelectIntoVarList);
sis->sublink = sl;
sis->userVarList = uset->name;
uset->val = (Expr *)sis;
VariableSetStmt* vss = makeNode(VariableSetStmt);
vss->kind = VAR_SET_DEFINED;
vss->name = "SELECT INTO VARLIST";
vss->defined_args = list_make1((Node *)uset);
vss->is_local = false;
vss->is_multiset = true;
VariableMultiSetStmt* vmss = makeNode(VariableMultiSetStmt);
vmss->args = list_make1((Node *)vss);
return (Node *)vmss;
} else if (intoClause->filename) {
CopyStmt* cn = makeNode(CopyStmt);
cn->relation = NULL;
cn->attlist = NIL;
cn->is_from = false;
cn->options = intoClause->copyOption;
cn->filename = intoClause->filename;
cn->filetype = intoClause->is_outfile ? S_OUTFILE : S_DUMPFILE;
cn->query = parseTree;
return (Node*)cn;
} else {
CreateTableAsStmt* ctas = makeNode(CreateTableAsStmt);
ctas->query = parseTree;
ctas->into = intoClause;
ctas->relkind = OBJECT_TABLE;
ctas->is_select_into = true;
return (Node*)ctas;
}
}
/*
* transformTopLevelStmt -
* transform a Parse tree into a Query tree.
*
* The only thing we do here that we don't do in transformStmt() is to
* convert SELECT ... INTO into CREATE TABLE AS. Since utility statements
* aren't allowed within larger statements, this is only allowed at the top
* of the parse tree, and so we only try it before entering the recursive
* transformStmt() processing.
*/
Query* transformTopLevelStmt(ParseState* pstate, Node* parseTree, bool isFirstNode, bool isCreateView)
{
if (IsA(parseTree, SelectStmt)) {
SelectStmt* stmt = (SelectStmt*)parseTree;
/* If it's a set-operation tree, drill down to leftmost SelectStmt */
while (stmt != NULL && stmt->op != SETOP_NONE)
stmt = stmt->larg;
AssertEreport(stmt && IsA(stmt, SelectStmt) && stmt->larg == NULL, MOD_OPT, "failure to check parseTree");
if (stmt->intoClause) {
parseTree = parse_into_claues(parseTree, stmt->intoClause);
/*
* Remove the intoClause from the SelectStmt. This makes it safe
* for lexical_select_stmt to complain if it finds intoClause set
* (implying that the INTO appeared in a disallowed place).
*/
stmt->intoClause = NULL;
}
}
if (u_sess->hook_cxt.transformStmtHook != NULL) {
return
((transformStmtFunc)(u_sess->hook_cxt.transformStmtHook))(pstate, parseTree, isFirstNode, isCreateView);
}
return transformStmt(pstate, parseTree, isFirstNode, isCreateView);
}
Query* transformCreateModelStmt(ParseState* pstate, CreateModelStmt* stmt)
{
SelectStmt* select_stmt = (SelectStmt*) stmt->select_query;
stmt->algorithm = get_algorithm_ml(stmt->architecture);
if (stmt->algorithm == INVALID_ALGORITHM_ML) {
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Non recognized ML model architecture definition %s", stmt->architecture)));
}
if (SearchSysCacheExists1(DB4AI_MODEL, CStringGetDatum(stmt->model))) {
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("The model name \"%s\" already exists in gs_model_warehouse.", stmt->model)));
}
// Create the projection for the AI operator in the query plan
// If the algorithm is supervised, the target is always the first element of the list
if (is_supervised(stmt->algorithm)) {
if (list_length(stmt->model_features) == 0) {
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Supervised ML algorithms require FEATURES clause")));
}else if (list_length(stmt->model_target) == 0) {
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Supervised ML algorithms require TARGET clause")));
}else if (list_length(stmt->model_target) > 1) {
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Target clause only supports one expression")));
}
}else{
if (list_length(stmt->model_target) > 0) {
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Unsupervised ML algorithms cannot have TARGET clause")));
}
}
select_stmt->targetList = NULL;
foreach_cell(it, stmt->model_target) {
select_stmt->targetList = lappend(select_stmt->targetList, lfirst(it));
}
if (list_length(stmt->model_features) > 0) { // User given projection
foreach_cell(it, stmt->model_features) {
select_stmt->targetList = lappend(select_stmt->targetList, lfirst(it));
}
} else { // No projection
ResTarget *rt = makeNode(ResTarget);
ColumnRef *cr = makeNode(ColumnRef);
cr->fields = list_make1(makeNode(A_Star));
cr->location = -1;
rt->name = NULL;
rt->indirection = NIL;
rt->val = (Node *)cr;
rt->location = -1;
select_stmt->targetList = lappend(select_stmt->targetList, rt);
}
// Transform the select query that we prepared for the training operator
Query* select_query = transformStmt(pstate, (Node*) select_stmt);
stmt->select_query = (Node*) select_query;
/* represent the command as a utility Query */
Query* result = makeNode(Query);
result->commandType = CMD_UTILITY;
result->utilityStmt = (Node*)stmt;
return result;
}
Query* transformVariableCreateEventStmt(ParseState* pstate, CreateEventStmt* stmt)
{
Query* result = makeNode(Query);
result->commandType = CMD_UTILITY;
Node* old_time_expr = NULL;
Node* new_time_expr = NULL;
if (stmt->start_time_expr) {
old_time_expr = stmt->start_time_expr;
new_time_expr = transformExprRecurse(pstate, old_time_expr);
stmt->start_time_expr = new_time_expr;
}
if (stmt->end_time_expr) {
old_time_expr = stmt->end_time_expr;
new_time_expr = transformExprRecurse(pstate, old_time_expr);
stmt->end_time_expr = new_time_expr;
}
result->utilityStmt = (Node*)stmt;
return result;
}
Query* transformVariableAlterEventStmt(ParseState* pstate, AlterEventStmt* stmt)
{
Query* result = makeNode(Query);
result->commandType = CMD_UTILITY;
Node* old_time_expr = NULL;
Node* new_time_node = NULL;
DefElem* new_time_expr = NULL;
if (stmt->start_time_expr) {
old_time_expr = stmt->start_time_expr->arg;
new_time_node = transformExprRecurse(pstate, old_time_expr);
if (new_time_node) {
new_time_expr = makeDefElem("start_date", new_time_node);
} else {
new_time_expr = NULL;
}
stmt->start_time_expr = new_time_expr;
}
if (stmt->end_time_expr) {
old_time_expr = stmt->end_time_expr->arg;
new_time_node = transformExprRecurse(pstate, old_time_expr);
if (new_time_node) {
new_time_expr = makeDefElem("start_date", new_time_node);
} else {
new_time_expr = NULL;
}
stmt->end_time_expr = new_time_expr;
}
result->utilityStmt = (Node*)stmt;
return result;
}
/*
* transformStmt -
* recursively transform a Parse tree into a Query tree.
*/
Query* transformStmt(ParseState* pstate, Node* parseTree, bool isFirstNode, bool isCreateView)
{
Query* result = NULL;
AnalyzerRoutine *analyzerRoutineHook = (AnalyzerRoutine*)u_sess->hook_cxt.analyzerRoutineHook;
if (u_sess->attr.attr_common.enable_expr_fusion && u_sess->attr.attr_sql.query_dop_tmp == 1) {
pstate->p_is_flt_frame = true;
}
switch (nodeTag(parseTree)) {
/*
* Optimizable statements
*/
case T_InsertStmt:
result = transformInsertStmt(pstate, (InsertStmt*)parseTree);
break;
case T_DeleteStmt:
result = transformDeleteStmt(pstate, (DeleteStmt*)parseTree);
break;
case T_UpdateStmt:
result = transformUpdateStmt(pstate, (UpdateStmt*)parseTree);
break;
case T_MergeStmt:
result = transformMergeStmt(pstate, (MergeStmt*)parseTree);
break;
case T_SelectStmt: {
SelectStmt* n = (SelectStmt*)parseTree;
if (n->valuesLists) {
result = transformValuesClause(pstate, n);
} else if (n->op == SETOP_NONE) {
if (analyzerRoutineHook == NULL || analyzerRoutineHook->transSelect == NULL) {
result = transformSelectStmt(pstate, n, isFirstNode, isCreateView);
} else {
result = analyzerRoutineHook->transSelect(pstate, n, isFirstNode, isCreateView);
}
} else {
result = transformSetOperationStmt(pstate, n);
}
} break;
/*
* Special cases
*/
case T_DeclareCursorStmt:
result = transformDeclareCursorStmt(pstate, (DeclareCursorStmt*)parseTree);
break;
case T_ExplainStmt:
result = transformExplainStmt(pstate, (ExplainStmt*)parseTree);
break;
#ifdef PGXC
case T_ExecDirectStmt:
result = transformExecDirectStmt(pstate, (ExecDirectStmt*)parseTree);
break;
#endif
case T_CreateTableAsStmt:
result = transformCreateTableAsStmt(pstate, (CreateTableAsStmt*)parseTree);
break;
case T_CreateModelStmt:
result = transformCreateModelStmt(pstate, (CreateModelStmt*) parseTree);
break;
case T_PrepareStmt: {
PrepareStmt* n = (PrepareStmt *)parseTree;
if (IsA(n->query, UserVar)) {
Node *uvar = transformExpr(pstate, n->query, EXPR_KIND_OTHER);
n->query = (Node *)copyObject((UserVar *)uvar);
}
result = makeNode(Query);
result->commandType = CMD_UTILITY;
result->utilityStmt = (Node*)parseTree;
} break;
case T_VariableSetStmt: {
VariableSetStmt* stmt = (VariableSetStmt*)parseTree;
if (DB_IS_CMPT(B_FORMAT) && stmt->kind == VAR_SET_VALUE &&
(u_sess->attr.attr_common.enable_set_variable_b_format || ENABLE_SET_VARIABLES)) {
transformVariableSetValueStmt(pstate, stmt);
}
result = makeNode(Query);
result->commandType = CMD_UTILITY;
result->utilityStmt = (Node*)parseTree;
} break;
case T_VariableMultiSetStmt:
result = transformVariableMutiSetStmt(pstate, (VariableMultiSetStmt*)parseTree);
break;
case T_CreateEventStmt:
result = transformVariableCreateEventStmt(pstate, (CreateEventStmt*) parseTree);
break;
case T_AlterEventStmt:
result = transformVariableAlterEventStmt(pstate, (AlterEventStmt*) parseTree);
break;
default:
/*
* other statements don't require any transformation; just return
* the original parsetree with a Query node plastered on top.
*/
result = makeNode(Query);
result->commandType = CMD_UTILITY;
result->utilityStmt = (Node*)parseTree;
break;
}
/* To be compatible before multi-relation modification supported. */
result->resultRelation = linitial2_int(result->resultRelations);
/* Mark as original query until we learn differently */
result->querySource = QSRC_ORIGINAL;
result->canSetTag = true;
/* Mark whether synonym object is in rtables or not. */
result->hasSynonyms = pstate->p_hasSynonyms;
result->is_flt_frame = pstate->p_is_flt_frame && !IS_ENABLE_RIGHT_REF(pstate->rightRefState);
if (nodeTag(parseTree) != T_InsertStmt) {
result->rightRefState = nullptr;
}
return result;
}
/*
* analyze_requires_snapshot
* Returns true if a snapshot must be set before doing parse analysis
* on the given raw parse tree.
*
* Classification here should match transformStmt(); but we also have to
* allow a NULL input (for Parse/Bind of an empty query string).
*/
bool analyze_requires_snapshot(Node* parseTree)
{
bool result = false;
if (parseTree == NULL)
return false;
switch (nodeTag(parseTree)) {
/*
* Optimizable statements
*/
case T_InsertStmt:
case T_DeleteStmt:
case T_UpdateStmt:
case T_MergeStmt:
case T_SelectStmt:
result = true;
break;
/*
* Special cases
*/
case T_DeclareCursorStmt:
/* yes, because it's analyzed just like SELECT */
result = true;
break;
case T_ExplainStmt:
case T_CreateTableAsStmt:
/* yes, because we must analyze the contained statement */
result = true;
break;
#ifdef PGXC
case T_ExecDirectStmt:
/*
* We will parse/analyze/plan inner query, which probably will
* need a snapshot. Ensure it is set.
*/
result = true;
break;
#endif
case T_RefreshMatViewStmt:
/* yes, because the SELECT from pg_rewrite must be analyzed */
if ((!((RefreshMatViewStmt *)parseTree)->incremental) || IS_PGXC_DATANODE) {
result = true;
}
break;
case T_VariableMultiSetStmt:
/* user-defined variables support sublink */
result = true;
break;
default:
/* other utility statements don't have any real parse analysis */
result = false;
break;
}
return result;
}
/*
* Description: check if the delete stmt is for 'plan_table'. If delete mutil relations, report err.
* Parameters:
* @in relations: the delete tables list.
* Return: ture if the object name is plan_table
*/
static bool checkDeleteStmtForPlanTable(List* relations)
{
OnlyDeleteFromPlanTable = false;
const char* target_rel = V_PLAN_TABLE;
ListCell *lc = NULL;
Oid plan_table_data_oid = RelnameGetRelid(T_PLAN_TABLE_DATA);
foreach (lc, relations) {
RangeVar* rv = (RangeVar*)lfirst(lc);
if (strcasecmp(rv->relname, target_rel) == 0 && plan_table_data_oid != InvalidOid) {
if (list_length(relations) > 1) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("plan_table cannot be deleted in mutiple tables delete")));
} else {
return true;
}
}
}
return false;
}
/*
* Description: make SessionIdExpr for WhereClause.
* Parameters:
* @in cur_location: current location.
* @out lexpr: the left expr.
* @out rexpr: the right expr.
* @out op_loc: operation location.
* Return: void
*/
static void makeSessionIdExpr(Node** lexpr, Node** rexpr, int* cur_location, int* op_loc)
{
/* make filter condition for session_id */
char* lcolname = "session_id";
ColumnRef* c = makeNode(ColumnRef);
c->fields = list_make1(makeString(lcolname));
c->location = *cur_location;
*cur_location = *cur_location + strlen("session_id=");
*op_loc = *cur_location - 1;
char* s_id = (char*)palloc0(SESSION_ID_LEN);
getSessionID(s_id,
IS_THREAD_POOL_WORKER ? u_sess->proc_cxt.MyProcPort->SessionStartTime : t_thrd.proc_cxt.MyStartTime,
IS_THREAD_POOL_WORKER ? u_sess->session_id : t_thrd.proc_cxt.MyProcPid);
A_Const* n = makeNode(A_Const);
n->val.type = T_String;
n->val.val.str = s_id;
n->location = *cur_location;
*cur_location = *cur_location + strlen(s_id) + 3;
*lexpr = (Node*)c;
*rexpr = (Node*)n;
}
/*
* Description: make UserIdExpr for WhereClause.
* Parameters:
* @in cur_location: current location.
* @out lexpr: the left expr.
* @out rexpr: the right expr.
* @out op_loc: operation location.
* Return: void
*/
static void makeUserIdExpr(Node** lexpr, Node** rexpr, int* cur_location, int* op_loc)
{
/* for filter condition user_id */
char* lcolname = "user_id";
ColumnRef* c = makeNode(ColumnRef);
c->fields = list_make1(makeString(lcolname));
c->location = *cur_location;
*cur_location = *cur_location + strlen("user_id=");
*op_loc = *cur_location - 1;
Oid user_id = GetCurrentUserId();
A_Const* n = makeNode(A_Const);
n->val.type = T_Integer;
n->val.val.ival = user_id;
n->location = *cur_location;
*lexpr = (Node*)c;
*rexpr = (Node*)n;
}
/*
* Description: make WhereClause node including SessionIdExpr and UserIdExpr for plan_table detele stmt.
* Parameters:
* @in cur_location: current location.
* @out whereClause: WhereClause that we made.
* Return: void
*/
static void makeNewWhereClause(Node** whereClause, int* cur_location)
{
Node* lexpr = NULL;
Node* rexpr = NULL;
A_Expr* clause_1 = NULL;
A_Expr* clause_2 = NULL;
int op_loc = -1;
int and_loc = -1;
makeSessionIdExpr(&lexpr, &rexpr, cur_location, &op_loc);
clause_1 = makeSimpleA_Expr(AEXPR_OP, "=", lexpr, rexpr, op_loc);
and_loc = *cur_location;
*cur_location = *cur_location + strlen("AND ");
makeUserIdExpr(&lexpr, &rexpr, cur_location, &op_loc);
clause_2 = makeSimpleA_Expr(AEXPR_OP, "=", lexpr, rexpr, op_loc);
*whereClause = (Node*)makeA_Expr(AEXPR_AND, NIL, (Node*)clause_1, (Node*)clause_2, and_loc);
}
/*
* Description: add WhereClause for plan_table detele stmt.
* Parameters:
* @in pstate: ParseState.
* @int stmt: delete stmt that user input.
* Return: DeleteStmt that plus new WhereClause.
*/
static DeleteStmt* addWhereClauseForPlanTable(ParseState* pstate, DeleteStmt* stmt)
{
int current_location = -1;
DeleteStmt* new_stmt = (DeleteStmt*)copyObject(stmt);
((RangeVar*)linitial(new_stmt->relations))->relname = T_PLAN_TABLE_DATA;
current_location = strlen("delete from plan_table_data where ");
/* make a new WhereClause include session_id and user_id filter condition. */
if (new_stmt->whereClause == NULL) {
makeNewWhereClause(&new_stmt->whereClause, &current_location);
} else {
/* add a WhereClause include session_id and user_id filter condition, behind original WhereClause. */
makeNewWhereClause(&new_stmt->whereClause, &current_location);
new_stmt->whereClause = (Node*)makeA_Expr(AEXPR_AND, NIL, new_stmt->whereClause, stmt->whereClause, -1);
}
return new_stmt;
}
const void SendCommandIdForInsertCte(Query* qry, WithClause* withclause)
{
ListCell* tl = NULL;
/*
* For a WITH query that deletes from a parent table in the
* main query & inserts a row in the child table in the WITH query
* we need to use command ID communication to remote nodes in order
* to maintain global data visibility.
* For example
* CREATE TEMP TABLE parent ( id int, val text ) DISTRIBUTE BY REPLICATION;
* CREATE TEMP TABLE child ( ) INHERITS ( parent ) DISTRIBUTE BY REPLICATION;
* INSERT INTO parent VALUES ( 42, 'old' );
* INSERT INTO child VALUES ( 42, 'older' );
* WITH wcte AS ( INSERT INTO child VALUES ( 42, 'new' ) RETURNING id AS newid )
* DELETE FROM parent USING wcte WHERE id = newid;
* The last query gets translated into the following multi-statement
* transaction on the primary datanode
* (a) SELECT id, ctid FROM ONLY parent WHERE true
* (b) START TRANSACTION ISOLATION LEVEL read committed READ WRITE
* (c) INSERT INTO child (id, val) VALUES ($1, $2) RETURNING id -- (42, 'new')
* (d) DELETE FROM ONLY parent parent WHERE (parent.ctid = $1)
* (e) SELECT id, ctid FROM ONLY child parent WHERE true
* (f) DELETE FROM ONLY child parent WHERE (parent.ctid = $1)
* (g) COMMIT TRANSACTION
* The command id of the select in step (e), should be such that
* it does not see the insert of step (c)
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
foreach (tl, withclause->ctes) {
CommonTableExpr* cte = (CommonTableExpr*)lfirst(tl);
if (IsA(cte->ctequery, InsertStmt)) {
qry->has_to_save_cmd_id = true;
SetSendCommandId(true);
break;
}
}
}
}
/*
* CheckTsDelete - check if delete by timerange
*/
#ifdef ENABLE_MULTIPLE_NODES
/*
* check delete by time range or not
*/
static bool check_del_timerange(const Node* qual)
{
if (qual == NULL) {
return false;
}
OpExpr* opexpr = (OpExpr*)qual;
const int filter_param_num = 2;
if ((nodeTag(qual) == T_OpExpr) && (list_length(opexpr->args) == filter_param_num) &&
((opexpr->opfuncid <= INTERVALINFUNCOID && opexpr->opfuncid >= TIMESTAMPTZOUTFUNCOID) ||
(opexpr->opfuncid <= TIMESTAMPTZCMPTIMESTAMPFUNCOID &&
opexpr->opfuncid >= TIMESTAMPLTTIMESTAMPTZFUNCOID) ||
(opexpr->opfuncid <= TimeOprId::TIMESTAMP_OP_MAX &&
opexpr->opfuncid >= TimeOprId::TIMESTAMP_OP_MIN) ||
(opexpr->opfuncid <= TimeOprId::TIMESTAMP_DATE_OP_MAX &&
opexpr->opfuncid >= TimeOprId::TIMESTAMP_DATE_OP_MIN))) {
return true;
} else if ((nodeTag(qual) == T_BoolExpr) && (((BoolExpr*)qual)->boolop == OR_EXPR)) {
bool check_time_qual = true;
ListCell* cell = NULL;
/* if is or expr, we must confirm each sub-clause has time range in least 1 hour */
for (cell = list_head(((BoolExpr*)qual)->args); cell != NULL && check_time_qual; cell = lnext(cell)) {
check_time_qual = check_time_qual && check_del_timerange((Node*)lfirst(cell));
}
return check_time_qual;
} else if ((nodeTag(qual) == T_BoolExpr) && (((BoolExpr*)qual)->boolop == AND_EXPR)) {
bool check_time_qual = false;
ListCell* cell = NULL;
/* if is and expr, we can just have one sub-clause has time range in least 1 hour */
for (cell = list_head(((BoolExpr*)qual)->args); cell != NULL; cell = lnext(cell)) {
check_time_qual = check_time_qual || check_del_timerange((Node*)lfirst(cell));
}
return check_time_qual;
}
return false;
}
static void CheckTsDelete(const ParseState* pstate, const Query* qry)
{
ListCell* l;
if (pstate == NULL || qry == NULL) {
ereport(ERROR, (errmodule(MOD_TIMESERIES), errmsg("Parsing null pointer to CheckTsDelete!")));
}
foreach (l, pstate->p_target_relation) {
Relation targetrel = (Relation)lfirst(l);
if (g_instance.attr.attr_common.enable_tsdb && RelationIsTsStore(targetrel)) {
if (qry->jointree == NULL) {
ereport(ERROR, (errmodule(MOD_TIMESERIES), errmsg("Timeseries only supports deletion by time range!")));
} else {
Node* quals = qry->jointree->quals;
if (!check_del_timerange(quals)) {
ereport(ERROR, (errmodule(MOD_TIMESERIES),
errmsg("Timeseries only supports deletion by time range!")));
}
}
}
}
return;
}
#endif /* ENABLE_MULTIPLE_NODES */
static bool IsSupportDeleteLimit(Relation rel, bool hasLimit)
{
RelationLocInfo* relLocInfo = NULL;
if (rel == NULL) {
return true;
}
relLocInfo = rel->rd_locator_info;
if (relLocInfo == NULL) {
return true;
}
if (IsRelationReplicated(relLocInfo) && hasLimit) {
return false;
}
return true;
}
/*
* transformUpdateSortClause -
* transforms an update statement sort clause
*/
static List* transformUpdateSortClause(ParseState* pstate, UpdateStmt* stmt, Query* qry)
{
ListCell* lc = NULL;
TargetEntry* tle = NULL;
if (stmt->sortClause == NULL) {
return NULL;
}
int len = list_length(qry->targetList);
foreach(lc, qry->targetList) {
tle = (TargetEntry*)lfirst(lc);
tle->resjunk = true;
}
List* rlst = transformSortClause(pstate, stmt->sortClause, &qry->targetList, EXPR_KIND_ORDER_BY, true, false);
int i = 0;
foreach(lc, qry->targetList) {
tle = (TargetEntry*)lfirst(lc);
tle->resjunk = false;
if (++i >= len) {
break;
}
}
return rlst;
}
/*
* transformLimitSortClause -
* transforms an delete|update statement limit and sort clause
*/
static void transformLimitSortClause(ParseState* pstate, void* stmt, Query* qry, bool forDel)
{
List *rlist = NULL;
qry->limitCount = forDel ? transformLimitClause(pstate, ((DeleteStmt*)stmt)->limitClause, EXPR_KIND_LIMIT, "LIMIT") :
transformLimitClause(pstate, ((UpdateStmt*)stmt)->limitClause, EXPR_KIND_LIMIT, "LIMIT");
if (!IsSupportDeleteLimit((Relation)linitial(pstate->p_target_relation), (qry->limitCount != NULL))) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
forDel ? errdetail("replication table doesn't allow DELETE LIMIT") :
errdetail("replication table doesn't allow UPDATE LIMIT")));
}
rlist = forDel ? transformSortClause(pstate, ((DeleteStmt*)stmt)->sortClause, &qry->targetList, EXPR_KIND_ORDER_BY, true, false) :
transformUpdateSortClause(pstate, (UpdateStmt*)stmt, qry);
/*
* For update statement, the effective range of sort clause is not optimized. For delete statement,
* sorting is performed only when limit or returning clause takes effect.
*/
if (qry->limitCount != NULL) {
/* flag for discriminating rownum */
Const *flag = makeNode(Const);
flag->ismaxvalue = true;
flag->location = -1;
flag->consttype = INT8OID;
flag->consttypmod = -1;
qry->limitOffset = (Node*)flag;
qry->sortClause = rlist;
} else if (!forDel || qry->returningList != NULL) {
qry->sortClause = rlist;
}
}
static void CheckRelationSupportMultiModify(Relation targetrel, bool forDel)
{
const char* type = NULL;
if (forDel) {
CheckDeleteRelation(targetrel);
type = "DELETE";
} else {
CheckUpdateRelation(targetrel);
type = "UPDATE";
}
if (RelationIsColStore(targetrel)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("column stored relation in mutilple-relations modifying doesn't support %s", type)));
}
if (RelationIsForeignTable(targetrel) || RelationIsStream(targetrel)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("FDW relation in mutilple-relations modifying doesn't support %s", type)));
}
if (targetrel->rd_rel->relhasrules) {
if (RelationIsView(targetrel) && (targetrel->rd_rules->numLocks > 1 ||
view_has_instead_trigger(targetrel, forDel ? CMD_DELETE : CMD_UPDATE))) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("view has rules or tiggers in mutilple-relations modifying doesn't support %s",
type)));
} else if (!RelationIsView(targetrel)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("relation has rules in mutilple-relations modifying doesn't support %s", type)));
} else {
/* recursive check for view */
Query* viewquery = get_view_query(targetrel);
RangeTblRef* rtr = NULL;
RangeTblEntry* base_rte = NULL;
Relation base_rel;
/* Ignore here, and it would throw an error during query rewrite. */
if (list_length(viewquery->jointree->fromlist) != 1) {
return;
}
rtr = (RangeTblRef*)linitial(viewquery->jointree->fromlist);
/* Ignore here, it would throw an error during query rewrite. */
if (!IsA(rtr, RangeTblRef)) {
return;
}
base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
base_rel = try_relation_open(base_rte->relid, AccessShareLock);
if (base_rel != NULL) {
CheckRelationSupportMultiModify(base_rel, forDel);
relation_close(base_rel, AccessShareLock);
}
}
}
}
static void CheckUDRelations(ParseState* pstate, List* sortClause, Node* limitClause, List* returningList,
bool forDel)
{
if (list_length(pstate->p_target_relation) == 1) {
if (forDel) {
CheckDeleteRelation((Relation)linitial(pstate->p_target_relation));
} else {
CheckUpdateRelation((Relation)linitial(pstate->p_target_relation));
}
return;
}
const char* type = forDel ? "DELETE" : "UPDATE";
if (sortClause != NIL) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("mutilple-relations modifying doesn't support %s ORDER BY", type)));
}
if (limitClause != NULL) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("mutilple-relations modifying doesn't support %s LIMIT", type)));
}
if (returningList != NIL) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("mutilple-relations modifying doesn't support %s returning", type)));
}
foreach_cell (l, pstate->p_target_relation) {
Relation targetrel = (Relation)lfirst(l);
CheckRelationSupportMultiModify(targetrel, forDel);
}
}
static void CheckDeleteRelation(Relation targetrel)
{
// check hdfs relation distributed by replication
if (targetrel->rd_locator_info &&
IsLocatorReplicated(targetrel->rd_locator_info->locatorType) &&
RelationIsPAXFormat(targetrel)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("hdfs replication table doesn't allow DELETE")));
}
#ifdef ENABLE_MULTIPLE_NODES
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
#endif
if (RelationIsMatview(targetrel)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unsupported feature"),
errdetail("Materialized view doesn't allow DELETE")));
}
#ifdef ENABLE_MULTIPLE_NODES
}
#endif
/* check delete permission */
if (((unsigned int)RelationGetInternalMask(targetrel) & INTERNAL_MASK_DDELETE)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("internal relation doesn't allow DELETE")));
}
// @Online expansion: check if the target relation is being redistributed in read only mode
if (!u_sess->attr.attr_sql.enable_cluster_resize && targetrel &&
RelationInClusterResizingWriteErrorMode(targetrel)) {
ereport(ERROR,
(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
errmsg("%s is redistributing, please retry later.", targetrel->rd_rel->relname.data)));
}
}
/*
* transformDeleteStmt -
* transforms a Delete Statement
*/
static Query* transformDeleteStmt(ParseState* pstate, DeleteStmt* stmt)
{
Query* qry = makeNode(Query);
ParseNamespaceItem *nsitem = NULL;
Node* qual = NULL;
/*
* Check the delete object name whether is 'plan_table'
* If it is, we will make a new where clause that include session_id and user_id filter condition.
*/
if (checkDeleteStmtForPlanTable(stmt->relations)) {
stmt = addWhereClauseForPlanTable(pstate, stmt);
OnlyDeleteFromPlanTable = true;
}
qry->commandType = CMD_DELETE;
/* set io state for backend status for the thread, we will use it to check user space */
pgstat_set_io_state(IOSTATE_READ);
/* process the WITH clause independently of all else */
if (stmt->withClause) {
#ifdef PGXC
SendCommandIdForInsertCte(qry, stmt->withClause);
#endif
qry->hasRecursive = stmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, stmt->withClause);
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
}
/*
* for sql_compatibility B, using clause may have the same relation name with result relations, so
* put transformFromClause here in advance to avoid checkNameSpaceConflicts with result relations.
*/
if (u_sess->attr.attr_sql.sql_compatibility == B_FORMAT) {
transformFromClause(pstate, stmt->usingClause);
}
/* set up range table with just the result rel */
qry->resultRelations =
setTargetTables(pstate, stmt->relations, true, true, ACL_DELETE);
CheckUDRelations(pstate, stmt->sortClause, stmt->limitClause, stmt->returningList, true);
/* there's no DISTINCT in DELETE */
qry->distinctClause = NIL;
/*
* The USING clause is non-standard SQL syntax, and is equivalent in
* functionality to the FROM list that can be specified for UPDATE. The
* USING keyword is used rather than FROM because FROM is already a
* keyword in the DELETE syntax.
*/
if (u_sess->attr.attr_sql.sql_compatibility != B_FORMAT) {
/* grab the namespace item made by setTargetTable */
nsitem = (ParseNamespaceItem *)llast(pstate->p_relnamespace);
/* subqueries in USING can see the result relation only via LATERAL */
nsitem->p_lateral_only = true;
transformFromClause(pstate, stmt->usingClause);
/* remaining clauses can see the result relation normally */
nsitem->p_lateral_only = false;
}
qual = transformWhereClause(pstate, stmt->whereClause, EXPR_KIND_WHERE, "WHERE");
Relation targetrel = (Relation)linitial(pstate->p_target_relation);
qry->returningList = transformReturningList(pstate, stmt->returningList);
if (pstate->p_target_relation && qry->returningList != NIL && RelationIsColStore(targetrel)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("column stored relation doesn't support DELETE returning")));
} else if (pstate->p_target_relation && qry->returningList != NIL && RelationIsTsStore(targetrel)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("Timeseries stored relation doesn't support DELETE returning")));
}
if (!checkAllowedTableCombination(pstate)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-supported feature"),
errdetail("UStore relations cannot be used with other storage types in DELETE statement")));
}
/* done building the range table and jointree */
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
transformLimitSortClause(pstate, stmt, qry, true);
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
if (pstate->p_hasWindowFuncs)
parseCheckWindowFuncs(pstate, qry);
qry->hasAggs = pstate->p_hasAggs;
assign_query_collations(pstate, qry);
/* this must be done after collations, for reliable comparison of exprs */
if (pstate->p_hasAggs)
parseCheckAggregates(pstate, qry);
qry->hintState = stmt->hintState;
#ifdef ENABLE_MULTIPLE_NODES
CheckTsDelete(pstate, qry);
#endif /* ENABLE_MULTIPLE_NODES */
return qry;
}
/* @hdfs
* brief: Check whether there is a openGauss forein table in qry. Return true means
* openGauss foreign table existed, false not existed
* input param @qry: the Query for foreign table
*/
static bool IsFindPgfdwForeignTbl(Query* qry)
{
List* rtable = qry->rtable;
ListCell* lc = NULL;
foreach (lc, rtable) {
RangeTblEntry* rte = (RangeTblEntry*)lfirst(lc);
if (rte->relkind == RELKIND_FOREIGN_TABLE || rte->relkind == RELKIND_STREAM) {
if (IS_POSTGRESFDW_FOREIGN_TABLE(rte->relid)) {
return true;
}
}
}
return false;
}
/* @hdfs
* brief: Check whether there is a HDFS forein table in qry. Return true means
* HDFS foreign table existed, false not existed
* input param @qry: the Query for foreign table
*/
static bool IsFindHDFSForeignTbl(Query* qry)
{
List* rtable = qry->rtable;
ListCell* lc = NULL;
foreach (lc, rtable) {
RangeTblEntry* rte = (RangeTblEntry*)lfirst(lc);
if (rte->relkind == RELKIND_FOREIGN_TABLE || rte->relkind == RELKIND_STREAM) {
if (isObsOrHdfsTableFormTblOid(rte->relid) || IS_OBS_CSV_TXT_FOREIGN_TABLE(rte->relid)) {
return true;
}
}
}
return false;
}
static Query* FindQueryContainForeignTbl(Query* qry)
{
List* rtable = qry->rtable;
ListCell* lc = NULL;
foreach (lc, rtable) {
RangeTblEntry* rte = (RangeTblEntry*)lfirst(lc);
if (rte->relkind == RELKIND_FOREIGN_TABLE || rte->relkind == RELKIND_STREAM) {
return qry;
} else if (rte->rtekind == RTE_SUBQUERY) {
return FindQueryContainForeignTbl(rte->subquery);
}
}
ListCell* target = NULL;
foreach (target, qry->targetList) {
TargetEntry* entry = (TargetEntry*)lfirst(target);
if (IsA(entry->expr, SubLink) && IsA(((SubLink*)entry->expr)->subselect, Query)) {
Query* q = FindQueryContainForeignTbl((Query*)((SubLink*)entry->expr)->subselect);
if (q != NULL)
return q;
}
}
return NULL;
}
#ifdef ENABLE_MOT
/*
* @brief: Expression_tree_walker callback function.
* Checks the entire RTE used by a query to identify their storage engine type (MOT or PAGE).
*/
static bool StorageEngineUsedWalker(Node* node, RTEDetectorContext* context)
{
if (node == NULL) {
return false;
}
if (IsA(node, RangeTblRef)) {
RangeTblRef* rtr = (RangeTblRef*)node;
Query* qry = (Query*)llast(context->queryNodes);
RangeTblEntry* rte = rt_fetch(rtr->rtindex, qry->rtable);
if (rte->rtekind == RTE_RELATION) {
if (rte->relkind == RELKIND_FOREIGN_TABLE && isMOTFromTblOid(rte->relid)) {
context->isMotTable = true;
} else if (!is_sys_table(rte->relid)) {
context->isPageTable = true;
}
}
return false;
}
if (IsA(node, Query)) {
/* Recurse into subselects */
bool result = false;
context->queryNodes = lappend(context->queryNodes, (Query*)node);
context->sublevelsUp++;
result = query_tree_walker((Query*)node, (bool (*)())StorageEngineUsedWalker, (void*)context, 0);
context->sublevelsUp--;
context->queryNodes = list_delete(context->queryNodes, llast(context->queryNodes));
return result;
}
return expression_tree_walker(node, (bool (*)())StorageEngineUsedWalker, (void*)context);
}
/*
* @brief: Analyze a query to check which storage engines are being used,
* PG, MOT, mixed PG & MOT or Other type.
* @input: Query to be analyzed.
* @output: Type of storage engine.
*/
void CheckTablesStorageEngine(Query* qry, StorageEngineType* type)
{
RTEDetectorContext context;
context.queryNodes = NIL;
context.sublevelsUp = 0;
context.isMotTable = false;
context.isPageTable = false;
*type = SE_TYPE_UNSPECIFIED;
/* check root node RTEs in case of non RangeTblRef nodes */
List* rtable = qry->rtable;
ListCell* lc = NULL;
foreach (lc, rtable) {
RangeTblEntry* rte = (RangeTblEntry*)lfirst(lc);
if (rte->rtekind == RTE_RELATION) {
if (rte->relkind == RELKIND_FOREIGN_TABLE && isMOTFromTblOid(rte->relid)) {
context.isMotTable = true;
} else if (!is_sys_table(rte->relid)) {
context.isPageTable = true;
}
}
}
/* MOT JIT: check if this is an invoke MOT JITted SP */
if (JitExec::IsInvokeReadyFunction(qry)) {
context.isMotTable = true;
}
/* add root query to query stack list */
context.queryNodes = lappend(context.queryNodes, qry);
/* recursive walk on the query */
(void)query_or_expression_tree_walker((Node*)qry, (bool (*)())StorageEngineUsedWalker, (void*)&context, 0);
if (context.isMotTable && context.isPageTable) {
*type = SE_TYPE_MIXED;
} else if (context.isPageTable) {
*type = SE_TYPE_PAGE_BASED;
} else if (context.isMotTable) {
*type = SE_TYPE_MOT;
}
}
#endif
static void CheckUnsupportInsertSelectClause(Query* query)
{
RangeTblEntry* result = NULL;
Query* subquery = NULL;
if (query == NULL)
return;
result = rt_fetch(linitial_int(query->resultRelations), query->rtable);
AssertEreport(query->commandType == CMD_INSERT, MOD_OPT, "Only deal with CMD_INSERT commondType here");
if (result->relkind == RELKIND_FOREIGN_TABLE || result->relkind == RELKIND_STREAM) {
if (CheckSupportedFDWType(result->relid)) {
return;
}
#ifdef ENABLE_MULTIPLE_NODES
if (relid_is_stream(result->relid)) {
if (t_thrd.streaming_cxt.loaded) {
return;
} else {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("Before insert stream object, please switch on the stream engine")));
}
}
#endif
if (list_length(query->rtable) == 1)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("insert statement is an INSERT INTO VALUES(...)")));
if (list_length(query->rtable) > 2)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("too complex subquery")));
RangeTblEntry* subrte = rt_fetch(linitial_int(query->resultRelations) == 1 ? 2 : 1, query->rtable);
/* non-subquery is unsupported */
if (subrte->rtekind != RTE_SUBQUERY)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("only subquery is supported")));
subquery = subrte->subquery;
if (list_length(subquery->rtable) != 1)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("subquery can not contain more than one relation")));
if (rt_fetch(1, subquery->rtable)->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("range table of subquery should be a normal relation")));
/* complex simple table query is not supported */
if (subquery->distinctClause)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("subquery contains DISTINCT clause")));
if (subquery->hasAggs || subquery->groupClause)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("subquery contains aggregation")));
if (subquery->hasWindowFuncs)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("subquery contains window function")));
if (subquery->hasSubLinks)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("subquery contains other subqueries")));
if (subquery->limitCount || subquery->limitOffset)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("subquery contains LIMIT clause")));
if (subquery->sortClause)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("subquery contains SORT clause")));
} else if (list_length(query->rtable) == 1) {
return;
} else if ((subquery = FindQueryContainForeignTbl(query)) != NULL) {
if (!IsFindHDFSForeignTbl(subquery) && !IsFindPgfdwForeignTbl(subquery)) {
if (list_length(query->rtable) > 2) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("too complex statement to foreign table")));
}
// check if SELECT clause contains JOIN foreign table
if (list_length(subquery->rtable) > 1) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("JOIN contains foreign table")));
}
RangeTblEntry* rte = rt_fetch(linitial_int(query->resultRelations) == 1 ? 2 : 1, query->rtable);
AssertEreport(rte->rtekind == RTE_SUBQUERY, MOD_OPT, "Only deal with subquery in FROM clause here");
if (rte->subquery != subquery) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("too complex statement to foreign table")));
}
// Unsupport LIMIT clause
if (subquery->limitCount != NULL || subquery->limitOffset != NULL) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("subquery contains LIMIT clause")));
}
}
}
}
static void SetInsertAttrnoState(ParseState* pstate, List* attrnos)
{
RightRefState* rstate = pstate->rightRefState;
Relation relation = (Relation)linitial(pstate->p_target_relation);
rstate->colCnt = RelationGetNumberOfAttributes(relation);
int len = list_length(attrnos);
rstate->explicitAttrLen = len;
rstate->explicitAttrNos = (int*)palloc0(sizeof(int) * len);
ListCell* attr = list_head(attrnos);
for (int i = 0; i < len; ++i) {
rstate->explicitAttrNos[i] = lfirst_int(attr);
attr = lnext(attr);
}
}
static void SetUpsertAttrnoState(ParseState* pstate, List *targetList)
{
if (!targetList) {
return;
}
RightRefState* rstate = pstate->rightRefState;
const int len = list_length(targetList);
rstate->usExplicitAttrLen = len;
rstate->usExplicitAttrNos = (int*)palloc0(sizeof(int) * len);
Relation relation = (Relation)linitial(pstate->p_target_relation);
FormData_pg_attribute* attr = relation->rd_att->attrs;
const int colNum = RelationGetNumberOfAttributes(relation);
ListCell* target = list_head(targetList);
for (int ni = 0; ni < len; ++ni) {
ResTarget* res = (ResTarget*)lfirst(target);
char* name = nullptr;
if (list_length(res->indirection) > 0) {
name = ((Value*)llast(res->indirection))->val.str;
} else {
name = res->name;
}
for (int ci = 0; ci < colNum; ++ci) {
if (attr[ci].attisdropped) {
continue;
}
if (strcmp(name, attr[ci].attname.data) == 0) {
rstate->usExplicitAttrNos[ni] = ci + 1;
break;
}
}
if (rstate->usExplicitAttrNos[ni] == InvalidOid) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("Column \"%s\" not found in upsert attrno state initialization", name)));
}
target = lnext(target);
}
}
static RightRefState* MakeRightRefState()
{
RightRefState* refState = (RightRefState*)palloc0(sizeof(RightRefState));
refState->isSupported = !IsInitdb && DB_IS_CMPT(B_FORMAT);
return refState;
}
/*
* transformInsertStmt -
* transform an Insert Statement
*/
static Query* transformInsertStmt(ParseState* pstate, InsertStmt* stmt)
{
Query* qry = makeNode(Query);
SelectStmt* selectStmt = (SelectStmt*)stmt->selectStmt;
List* exprList = NIL;
bool isGeneralSelect = false;
List* sub_rtable = NIL;
List* sub_relnamespace = NIL;
List* sub_varnamespace = NIL;
List* icolumns = NIL;
List* attrnos = NIL;
RangeTblEntry* rte = NULL;
RangeTblRef* rtr = NULL;
ListCell* icols = NULL;
ListCell* attnos = NULL;
ListCell* lc = NULL;
AclMode targetPerms = ACL_INSERT;
Relation targetrel = NULL;
/* There can't be any outer WITH to worry about */
AssertEreport(pstate->p_ctenamespace == NIL, MOD_OPT, "para should be NIL");
RightRefState* rightRefState = MakeRightRefState();
qry->commandType = CMD_INSERT;
pstate->p_is_insert = true;
pstate->p_has_ignore = stmt->hasIgnore;
pstate->rightRefState = rightRefState;
/* set io state for backend status for the thread, we will use it to check user space */
pgstat_set_io_state(IOSTATE_WRITE);
qry->isReplace = stmt->isReplace;
if (stmt->isReplace) {
targetPerms |= ACL_DELETE;
/* transform set clause in REPLACE INTO stmt */
if (selectStmt == NULL && stmt->cols == NULL && stmt->targetList != NULL) {
stmt->selectStmt = (Node *)makeNode(SelectStmt);
selectStmt = (SelectStmt *)stmt->selectStmt;
ListCell* o_target = NULL;
List *ctext_expr_list = NULL;
foreach (o_target, stmt->targetList) {
ResTarget* res = (ResTarget*)lfirst(o_target);
ctext_expr_list = lappend(ctext_expr_list, res->val);
}
selectStmt->valuesLists = list_make1(ctext_expr_list);
stmt->cols = (List *)copyObject(stmt->targetList);
foreach (o_target, stmt->cols) {
ResTarget* res = (ResTarget*)lfirst(o_target);
res->val = NULL;
}
}
}
/* process the WITH clause independently of all else */
if (stmt->withClause) {
qry->hasRecursive = stmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, stmt->withClause);
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
}
/*
* We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL),
* VALUES list, or general SELECT input. We special-case VALUES, both for
* efficiency and so we can handle DEFAULT specifications.
*
* The grammar allows attaching ORDER BY, LIMIT, FOR UPDATE, or WITH to a
* VALUES clause. If we have any of those, treat it as a general SELECT;
* so it will work, but you can't use DEFAULT items together with those.
*/
isGeneralSelect = (selectStmt && (selectStmt->valuesLists == NIL || selectStmt->sortClause != NIL ||
selectStmt->limitOffset != NULL || selectStmt->limitCount != NULL ||
selectStmt->lockingClause != NIL || selectStmt->withClause != NULL));
/*
* If a non-nil rangetable/namespace was passed in, and we are doing
* INSERT/SELECT, arrange to pass the rangetable/namespace down to the
* SELECT. This can only happen if we are inside a CREATE RULE, and in
* that case we want the rule's OLD and NEW rtable entries to appear as
* part of the SELECT's rtable, not as outer references for it. (Kluge!)
* The SELECT's joinlist is not affected however. We must do this before
* adding the target table to the INSERT's rtable.
*/
if (isGeneralSelect) {
sub_rtable = pstate->p_rtable;
pstate->p_rtable = NIL;
sub_relnamespace = pstate->p_relnamespace;
pstate->p_relnamespace = NIL;
sub_varnamespace = pstate->p_varnamespace;
pstate->p_varnamespace = NIL;
} else {
sub_rtable = NIL; /* not used, but keep compiler quiet */
sub_relnamespace = NIL;
sub_varnamespace = NIL;
}
/*
* Must get write lock on INSERT target table before scanning SELECT, else
* we will grab the wrong kind of initial lock if the target table is also
* mentioned in the SELECT part. Note that the target table is not added
* to the joinlist or namespace.
*/
if (stmt->upsertClause != NULL && stmt->upsertClause->targetList != NIL) {
targetPerms |= ACL_UPDATE;
}
qry->resultRelations = setTargetTables(pstate, list_make1(stmt->relation), false, false, targetPerms);
targetrel = (Relation)linitial(pstate->p_target_relation);
/*
* Insert into relation pg_auth_history is not allowed.
* We update it only when some user's password has been changed.
*/
if (targetrel != NULL && RelationGetRelid(targetrel) == AuthHistoryRelationId) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_OPERATION), errmsg("Not allowed to insert into relation pg_auth_history.")));
}
if (targetrel != NULL &&
((unsigned int)RelationGetInternalMask(targetrel) & INTERNAL_MASK_DINSERT)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("internal relation doesn't allow INSERT")));
}
#ifdef ENABLE_MULTIPLE_NODES
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
#endif
if (targetrel != NULL
&& RelationIsMatview(targetrel) && !stmt->isRewritten) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unsupported feature"),
errdetail("Materialized view doesn't allow INSERT")));
}
#ifdef ENABLE_MULTIPLE_NODES
}
#endif
if (targetrel != NULL && stmt->upsertClause != NULL) {
/* non-supported upsert cases */
if (!u_sess->attr.attr_sql.enable_upsert_to_merge && RelationIsColumnFormat(targetrel)) {
ereport(ERROR, ((errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("INSERT ON DUPLICATE KEY UPDATE is not supported on column orientated table."))));
}
if (RelationIsForeignTable(targetrel) || RelationIsStream(targetrel)) {
ereport(ERROR, ((errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("INSERT ON DUPLICATE KEY UPDATE is not supported on foreign table."))));
}
if (RelationIsView(targetrel)) {
ereport(ERROR, ((errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("INSERT ON DUPLICATE KEY UPDATE is not supported on VIEW."))));
}
if (RelationIsContquery(targetrel)) {
ereport(ERROR, ((errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("INSERT ON DUPLICATE KEY UPDATE is not supported on CONTQUERY."))));
}
}
/* non-supported IGNORE cases */
if (pstate->p_has_ignore && targetrel != NULL) {
if (RelationIsColumnFormat(targetrel)) {
ereport(ERROR, ((errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("IGNORE is not supported on INSERT column orientated table."))));
}
}
/* data redistribution for DFS table.
* check if the target relation is being redistributed(insert mode).
* For example, when table A is redistributing in session 1, operating table
* A with insert command in session 2, the table A can not be inserted data in session 2.
*
* We don't allow insert during online expansion when expansion is set as read only.
* We reply on gs_redis to ganurantee DFS table to be read only during online expansion
* so we don't need to double check if target table is DFS table here anymore.
*/
if (!u_sess->attr.attr_sql.enable_cluster_resize && targetrel != NULL &&
RelationInClusterResizingWriteErrorMode(targetrel)) {
ereport(ERROR,
(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
errmsg("%s is redistributing, please retry later.", targetrel->rd_rel->relname.data)));
}
/* Validate stmt->cols list, or build default list if no list given */
icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos);
SetInsertAttrnoState(pstate, attrnos);
AssertEreport(list_length(icolumns) == list_length(attrnos), MOD_OPT, "list length inconsistent");
/*
* Determine which variant of INSERT we have.
*/
if (selectStmt == NULL) {
/*
* We have INSERT ... DEFAULT VALUES. We can handle this case by
* emitting an empty targetlist --- all columns will be defaulted when
* the planner expands the targetlist.
*/
exprList = NIL;
} else if (isGeneralSelect) {
/*
* We make the sub-pstate a child of the outer pstate so that it can
* see any Param definitions supplied from above. Since the outer
* pstate's rtable and namespace are presently empty, there are no
* side-effects of exposing names the sub-SELECT shouldn't be able to
* see.
*/
ParseState* sub_pstate = make_parsestate(pstate);
Query* selectQuery = NULL;
#ifdef PGXC
RangeTblEntry* target_rte = NULL;
#endif
/*
* Process the source SELECT.
*
* It is important that this be handled just like a standalone SELECT;
* otherwise the behavior of SELECT within INSERT might be different
* from a stand-alone SELECT. (Indeed, Postgres up through 6.5 had
* bugs of just that nature...)
*/
sub_pstate->p_rtable = sub_rtable;
sub_pstate->p_joinexprs = NIL; /* sub_rtable has no joins */
sub_pstate->p_relnamespace = sub_relnamespace;
sub_pstate->p_varnamespace = sub_varnamespace;
/* Set sub_query is in insert */
sub_pstate->p_is_in_insert = true;
sub_pstate->p_resolve_unknowns = false;
selectQuery = transformStmt(sub_pstate, stmt->selectStmt);
Assert(selectQuery != NULL);
free_parsestate(sub_pstate);
/* The grammar should have produced a SELECT */
if (!IsA(selectQuery, Query) || selectQuery->commandType != CMD_SELECT || selectQuery->utilityStmt != NULL) {
ereport(
ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("unexpected non-SELECT command in INSERT ... SELECT")));
}
/*
* Make the source be a subquery in the INSERT's rangetable, and add
* it to the INSERT's joinlist.
*/
rte = addRangeTableEntryForSubquery(pstate, selectQuery, makeAlias("*SELECT*", NIL), false, false);
#ifdef PGXC
/*
* For an INSERT SELECT involving INSERT on a child after scanning
* the parent, set flag to send command ID communication to remote
* nodes in order to maintain global data visibility.
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
target_rte = rt_fetch(linitial_int(qry->resultRelations), pstate->p_rtable);
if (is_relation_child(target_rte, selectQuery->rtable)) {
qry->has_to_save_cmd_id = true;
SetSendCommandId(true);
}
}
#endif
rtr = makeNode(RangeTblRef);
/* assume new rte is at end */
rtr->rtindex = list_length(pstate->p_rtable);
AssertEreport(
rte == rt_fetch(rtr->rtindex, pstate->p_rtable), MOD_OPT, "check inconsistant with rt_fetch functon");
pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
/* ----------
* Generate an expression list for the INSERT that selects all the
* non-resjunk columns from the subquery. (INSERT's tlist must be
* separate from the subquery's tlist because we may add columns,
* insert datatype coercions, etc.)
*
* HACK: unknown-type constants and params in the SELECT's targetlist
* are copied up as-is rather than being referenced as subquery
* outputs. This is to ensure that when we try to coerce them to
* the target column's datatype, the right things happen (see
* special cases in coerce_type). Otherwise, this fails:
* INSERT INTO foo SELECT 'bar', ... FROM baz
* ----------
*/
int subpos = 0;
int respos = 0;
Bitmapset* subset = NULL;
Bitmapset* resset = NULL;
exprList = NIL;
foreach (lc, selectQuery->targetList) {
TargetEntry* tle = (TargetEntry*)lfirst(lc);
Expr* expr = NULL;
subpos++;
if (tle->resjunk) {
continue;
}
respos++;
if (tle->expr && (IsA(tle->expr, Const) || IsA(tle->expr, Param)) &&
exprType((Node*)tle->expr) == UNKNOWNOID) {
subset = bms_add_member(subset, subpos);
resset = bms_add_member(resset, respos);
expr = tle->expr;
} else {
Var* var = makeVarFromTargetEntry(rtr->rtindex, tle);
var->location = exprLocation((Node*)tle->expr);
expr = (Expr*)var;
}
exprList = lappend(exprList, expr);
}
/*
* If td_compatible_truncation equal true and no foreign table found,
* the auto truncation funciton should be enabled.
*/
if (u_sess->attr.attr_sql.sql_compatibility == C_FORMAT && targetrel != NULL &&
!RelationIsForeignTable(targetrel) &&
!RelationIsStream(targetrel)) {
if (u_sess->attr.attr_sql.td_compatible_truncation) {
pstate->p_is_td_compatible_truncation = true;
} else {
pstate->tdTruncCastStatus = NOT_CAST_BECAUSEOF_GUC;
}
}
/* Prepare row for assignment to target table */
exprList = transformInsertRow(pstate, exprList, stmt->cols, icolumns, attrnos);
if (!bms_is_empty(subset)) {
/*
* Try to coerce unknown-type constants and params to the target
* column's datatype.
*/
Assert(!bms_is_empty(resset));
while ((subpos = bms_first_member(subset)) > 0) {
TargetEntry* tle = (TargetEntry*)list_nth(selectQuery->targetList, subpos - 1);
respos = bms_first_member(resset);
Assert(respos > 0);
ListCell* rescell = list_nth_cell(exprList, respos - 1);
tle->expr = (Expr*)copyObject(lfirst(rescell));
/*
* After coercion, we need to transform those constants from
* the HACKY representation to a normal Var node like others.
*
* Old exprs in exprList are replaced by the newly
* generated Vars and then freed on each iteration.
*/
if ((IsA(tle->expr, Const) || IsA(tle->expr, Param)) &&
exprType((Node*)tle->expr) != UNKNOWNOID) {
lfirst(rescell) = (void*)makeVarFromTargetEntry(rtr->rtindex, tle);
}
}
}
} else if (list_length(selectStmt->valuesLists) > 1) {
/*
* Process INSERT ... VALUES with multiple VALUES sublists. We
* generate a VALUES RTE holding the transformed expression lists, and
* build up a targetlist containing Vars that reference the VALUES
* RTE.
*/
List* exprsLists = NIL;
List* collations = NIL;
int sublist_length = -1;
int i;
List* temp_list = NIL;
AssertEreport(selectStmt->intoClause == NULL, MOD_OPT, "into Clause should not happen here");
/*
* Here, because the auto truncation feature need to be blocked,
* once we found there is a foreign table involve. so here we change
* orignal one foreach loop to two foreach loops.
* first loop only transformExpressionList for all value lists and
* save temp result to temp_list. Here we also generate the result whether
* auto truncation is enable or not.
* Seconde loop will continues process the temp_list that first loop generated.
*/
foreach (lc, selectStmt->valuesLists) {
List* sublist = (List*)lfirst(lc);
/* Do basic expression transformation (same as a ROW() expr) */
sublist = transformExpressionList(pstate, sublist, EXPR_KIND_VALUES);
/*
* All the sublists must be the same length, *after*
* transformation (which might expand '*' into multiple items).
* The VALUES RTE can't handle anything different.
*/
if (sublist_length < 0) {
/* Remember post-transformation length of first sublist */
sublist_length = list_length(sublist);
} else if (sublist_length != list_length(sublist)) {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("VALUES lists must all be the same length"),
parser_errposition(pstate, exprLocation((Node*)sublist))));
}
/*
* If td_compatible_truncation equal true and no foreign table found,
* the auto truncation funciton should be enabled.
*/
if (u_sess->attr.attr_sql.sql_compatibility == C_FORMAT && targetrel != NULL &&
!RelationIsForeignTable(targetrel) &&
!RelationIsStream(targetrel)) {
if (u_sess->attr.attr_sql.td_compatible_truncation) {
pstate->p_is_td_compatible_truncation = true;
} else {
pstate->tdTruncCastStatus = NOT_CAST_BECAUSEOF_GUC;
}
}
temp_list = lappend(temp_list, sublist);
}
foreach (lc, temp_list) {
List* sublist = (List*)lfirst(lc);
/* Prepare row for assignment to target table */
sublist = transformInsertRow(pstate, sublist, stmt->cols, icolumns, attrnos);
/*
* We must assign collations now because assign_query_collations
* doesn't process rangetable entries. We just assign all the
* collations independently in each row, and don't worry about
* whether they are consistent vertically. The outer INSERT query
* isn't going to care about the collations of the VALUES columns,
* so it's not worth the effort to identify a common collation for
* each one here. (But note this does have one user-visible
* consequence: INSERT ... VALUES won't complain about conflicting
* explicit COLLATEs in a column, whereas the same VALUES
* construct in another context would complain.)
*/
assign_list_collations(pstate, sublist);
exprsLists = lappend(exprsLists, sublist);
}
/*
* Although we don't really need collation info, let's just make sure
* we provide a correctly-sized list in the VALUES RTE.
*/
for (i = 0; i < sublist_length; i++) {
collations = lappend_oid(collations, InvalidOid);
}
if (list_length(pstate->p_rtable) != 1 && contain_vars_of_level((Node*)exprsLists, 0)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VALUES must not contain OLD or NEW references"),
errhint("Use SELECT ... UNION ALL ... instead."),
parser_errposition(pstate, locate_var_of_level((Node*)exprsLists, 0))));
}
/*
* Generate the VALUES RTE
*/
rte = addRangeTableEntryForValues(pstate, exprsLists, collations, NULL, true);
rtr = makeNode(RangeTblRef);
/* assume new rte is at end */
rtr->rtindex = list_length(pstate->p_rtable);
AssertEreport(
rte == rt_fetch(rtr->rtindex, pstate->p_rtable), MOD_OPT, "check inconsistant with rt_fetch functon");
pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
/*
* Generate list of Vars referencing the RTE
*/
expandRTE(rte, rtr->rtindex, 0, -1, false, NULL, &exprList);
} else {
/* ----------
* Process INSERT ... VALUES with a single VALUES sublist.
* We treat this separately for efficiency and for historical
* compatibility --- specifically, allowing table references,
* such as
* INSERT INTO foo VALUES(bar.*)
*
* The sublist is just computed directly as the Query's targetlist,
* with no VALUES RTE. So it works just like SELECT without FROM.
* ----------
*/
List* valuesLists = selectStmt->valuesLists;
AssertEreport(list_length(valuesLists) == 1, MOD_OPT, "list length is wrong");
AssertEreport(selectStmt->intoClause == NULL, MOD_OPT, "intoClause should not happen here");
/* Do basic expression transformation (same as a ROW() expr) */
exprList = transformExpressionList(pstate, (List*)linitial(valuesLists), EXPR_KIND_VALUES_SINGLE);
/*
* If td_compatible_truncation equal true and no foreign table found,
* the auto truncation funciton should be enabled.
*/
if (u_sess->attr.attr_sql.sql_compatibility == C_FORMAT && targetrel != NULL &&
!RelationIsForeignTable(targetrel) &&
!RelationIsStream(targetrel)) {
if (u_sess->attr.attr_sql.td_compatible_truncation) {
pstate->p_is_td_compatible_truncation = true;
} else {
pstate->tdTruncCastStatus = NOT_CAST_BECAUSEOF_GUC;
}
}
/* Prepare row for assignment to target table */
exprList = transformInsertRow(pstate, exprList, stmt->cols, icolumns, attrnos);
}
/*
* Generate query's target list using the computed list of expressions.
* Also, mark all the target columns as needing insert permissions.
*/
rte = (RangeTblEntry*)linitial(pstate->p_target_rangetblentry);
qry->targetList = NIL;
icols = list_head(icolumns);
attnos = list_head(attrnos);
foreach (lc, exprList) {
Expr* expr = (Expr*)lfirst(lc);
ResTarget* col = NULL;
AttrNumber attr_num;
TargetEntry* tle = NULL;
col = (ResTarget*)lfirst(icols);
AssertEreport(IsA(col, ResTarget), MOD_OPT, "nodeType inconsistant");
attr_num = (AttrNumber)lfirst_int(attnos);
tle = makeTargetEntry(expr, attr_num, col->name, false);
qry->targetList = lappend(qry->targetList, tle);
rte->insertedCols = bms_add_member(rte->insertedCols, attr_num - FirstLowInvalidHeapAttributeNumber);
icols = lnext(icols);
attnos = lnext(attnos);
}
/* Process DUPLICATE KEY UPDATE, if any. */
if (stmt->upsertClause) {
if (IS_SUPPORT_RIGHT_REF(rightRefState)) {
pstate->p_varnamespace = NIL;
rightRefState->isUpsert = true;
SetUpsertAttrnoState(pstate, stmt->upsertClause->targetList);
qry->upsertClause = transformUpsertClause(pstate, stmt->upsertClause, qry->resultRelations);
rightRefState->isUpsert = false;
} else {
qry->upsertClause = transformUpsertClause(pstate, stmt->upsertClause, qry->resultRelations);
}
}
/*
* If we have a RETURNING clause, we need to add the target relation to
* the query namespace before processing it, so that Var references in
* RETURNING will work. Also, remove any namespace entries added in a
* sub-SELECT or VALUES list.
*/
if (stmt->returningList) {
pstate->p_relnamespace = NIL;
pstate->p_varnamespace = NIL;
pstate->rightRefState = nullptr;
addRTEtoQuery(pstate, rte, false, true, true);
qry->returningList = transformReturningList(pstate, stmt->returningList);
if (qry->returningList != NIL && RelationIsColStore(targetrel)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("column stored relation doesn't support INSERT returning")));
}
}
/* done building the range table and jointree */
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
qry->hasSubLinks = pstate->p_hasSubLinks;
/* aggregates not allowed (but subselects are okay) */
if (pstate->p_hasAggs) {
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in VALUES"),
parser_errposition(pstate, locate_agg_of_level((Node*)qry, 0))));
}
if (pstate->p_hasWindowFuncs) {
ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR),
errmsg("cannot use window function in VALUES"),
parser_errposition(pstate, locate_windowfunc((Node*)qry))));
}
assign_query_collations(pstate, qry);
CheckUnsupportInsertSelectClause(qry);
/* Set query tdTruncCastStatus to recored if auto truncation enabled or not. */
qry->tdTruncCastStatus = pstate->tdTruncCastStatus;
qry->hintState = stmt->hintState;
qry->hasIgnore = stmt->hasIgnore;
if (IS_ENABLE_RIGHT_REF(rightRefState)) {
qry->rightRefState = rightRefState;
} else {
qry->rightRefState = nullptr;
pstate->rightRefState = nullptr;
pfree(rightRefState);
}
return qry;
}
static void checkUpsertTargetlist(Relation targetTable, List* updateTlist)
{
List* index_list = RelationGetIndexInfoList(targetTable);
if (check_unique_constraint(index_list)) {
ListCell* target = NULL;
ListCell* index = NULL;
IndexInfo* index_info = NULL;
Bitmapset* target_attrs = NULL; /* attr bitmap according to targetlist */
Bitmapset* index_attrs = NULL; /* attr bitmap according to index */
TargetEntry* tle = NULL;
foreach (target, updateTlist) {
tle = (TargetEntry*)lfirst(target);
target_attrs = bms_add_member(target_attrs, tle->resno);
}
foreach (index, index_list) {
index_info = (IndexInfo*)lfirst(index);
for (int i = 0; i < index_info->ii_NumIndexAttrs; i++) {
int attrno = index_info->ii_KeyAttrNumbers[i];
if (attrno > 0) {
index_attrs = bms_add_member(index_attrs, attrno);
}
}
}
/* Allow in B_FORMAT */
if (bms_overlap(index_attrs, target_attrs) && u_sess->attr.attr_sql.sql_compatibility != B_FORMAT) {
ereport(ERROR, ((errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key."))));
}
}
}
List* BuildExcludedTargetlist(Relation targetrel, Index exclRelIndex)
{
List* result = NIL;
int attno;
Var* var = NULL;
TargetEntry* te = NULL;
/*
* Note that resnos of the tlist must correspond to attnos of the
* underlying relation, hence we need entries for dropped columns too.
*/
for (attno = 0; attno < RelationGetNumberOfAttributes(targetrel); attno++) {
Form_pg_attribute attr = &targetrel->rd_att->attrs[attno];
char* name = NULL;
if (attr->attisdropped) {
/*
* can't use atttypid here, but it doesn't really matter what type
* the Const claims to be.
*/
var = (Var*)makeNullConst(INT4OID, -1, InvalidOid);
} else {
var = makeVar(exclRelIndex, attno + 1, attr->atttypid, attr->atttypmod,
attr->attcollation, 0);
name = pstrdup(NameStr(attr->attname));
}
te = makeTargetEntry((Expr*)var, attno + 1, name, false);
result = lappend(result, te);
}
/*
* Add a whole-row-Var entry to support references to "EXCLUDED.*". Like
* the other entries in the EXCLUDED tlist, its resno must match the Var's
* varattno, else the wrong things happen while resolving references in
* setrefs.c. This is against normal conventions for targetlists, but
* it's okay since we don't use this as a real tlist.
*/
var = makeVar(exclRelIndex, InvalidAttrNumber, targetrel->rd_rel->reltype,
-1, InvalidOid, 0);
te = makeTargetEntry((Expr*)var, InvalidAttrNumber, NULL, true);
result = lappend(result, te);
return result;
}
static bool CheckRlsPolicyForUpsert(Relation targetrel)
{
ListCell *item = NULL;
RlsPolicy *policy = NULL;
List *relRlsPolicies = targetrel->rd_rlsdesc->rlsPolicies;
foreach (item, relRlsPolicies) {
policy = (RlsPolicy *)lfirst(item);
if (policy->cmdName == ACL_UPDATE_CHR || policy->cmdName == RLS_CMD_ALL_CHR) {
return true;
}
}
return false;
}
/*
* The following check only affect distributed deployment.
* Sublink in upsert's where clause is supported for centralized mode.
*/
#ifdef ENABLE_MULTIPLE_NODES
static bool ContainSubLinkWalker(Node* node, void* context)
{
if (node == NULL) {
return false;
}
if (IsA(node, SubLink)) {
return true;
}
return expression_tree_walker(node, (bool (*)())ContainSubLinkWalker, (void*)context);
}
static bool ContainSubLink(Node* clause)
{
return ContainSubLinkWalker(clause, NULL);
}
#endif /* ENABLE_MULTIPLE_NODES */
static UpsertExpr* transformUpsertClause(ParseState* pstate, UpsertClause* upsertClause, List* resultRelations)
{
UpsertExpr* result = NULL;
List* updateTlist = NIL;
RangeTblEntry* exclRte = NULL;
int exclRelIndex = 0;
List* exclRelTlist = NIL;
Node* updateWhere = NULL;
UpsertAction action = UPSERT_NOTHING;
Relation targetrel = (Relation)linitial(pstate->p_target_relation);
#ifdef ENABLE_MULTIPLE_NODES
if (targetrel->rd_rel->relhastriggers) {
ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("triggers are not supported and will be ignored by INSERT ON DUPLICATE KEY UPDATE.")));
}
#endif
if (RelationHasRlspolicy(targetrel->rd_id) && CheckRlsPolicyForUpsert(targetrel)) {
ereport(ERROR,
(errmodule(MOD_SEC_POLICY),
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Row level security policy is not supported by INSERT ON DUPLICATE KEY UPDATE."),
errdetail("Table %s with row level security policy.", RelationGetRelationName(targetrel)),
errcause("Target table with row level security policy."),
erraction("Shutdown row level security policy, Use INSERT and UPDATE instead INSERT ON DUPLICATE KEY "
"UPDATE.")));
}
if (upsertClause->targetList != NIL) {
pstate->p_is_insert = false;
action = UPSERT_UPDATE;
exclRte = addRangeTableEntryForRelation(pstate, targetrel, makeAlias("excluded", NIL), false, false);
exclRte->isexcluded = true;
exclRelIndex = list_length(pstate->p_rtable);
/*
* Build a targetlist for the EXCLUDED pseudo relation. Out of
* simplicity we do that here, because expandRelAttrs() happens to
* nearly do the right thing; specifically it also works with views.
* It'd be more proper to instead scan some pseudo scan node, but it
* doesn't seem worth the amount of code required.
*
* The only caveat of this hack is that the permissions expandRelAttrs
* adds have to be reset. markVarForSelectPriv() will add the exact
* required permissions back.
*/
exclRelTlist = BuildExcludedTargetlist(targetrel, exclRelIndex);
exclRte->requiredPerms = 0;
exclRte->selectedCols = NULL;
/*
* Add EXCLUDED and the target RTE to the namespace, so that they can
* be used in the UPDATE statement.
*/
addRTEtoQuery(pstate, exclRte, false, true, true);
addRTEtoQuery(pstate, (RangeTblEntry*)linitial(pstate->p_target_rangetblentry), false, true, true);
updateTlist = transformTargetList(pstate, upsertClause->targetList, EXPR_KIND_UPDATE_TARGET);
/* Done with select-like processing, move on transforming to match update set target column */
updateTlist = transformUpdateTargetList(pstate, updateTlist, upsertClause->targetList, resultRelations);
updateWhere = transformWhereClause(pstate, upsertClause->whereClause, EXPR_KIND_WHERE, "WHERE");
#ifdef ENABLE_MULTIPLE_NODES
/* Do not support sublinks in update where clause for now */
if (ContainSubLink(updateWhere)) {
ereport(ERROR,
(errmodule(MOD_OPT_PLANNER), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Feature is not supported for INSERT ON DUPLICATE KEY UPDATE."),
errdetail("Do not support sublink in where clause."),
errcause("Unsupported syntax."),
erraction("Check if the query can be rewritten into MERGE INTO statement "
"or reduce the sublink in where clause.")));
}
#endif /* ENABLE_MULTIPLE_NODES */
/* We can't update primary or unique key in upsert, check it here */
#ifdef ENABLE_MULTIPLE_NODES
if (IS_PGXC_COORDINATOR && !u_sess->attr.attr_sql.enable_upsert_to_merge) {
checkUpsertTargetlist(targetrel, updateTlist);
}
#else
checkUpsertTargetlist(targetrel, updateTlist);
#endif
}
/* Finally, build DUPLICATE KEY UPDATE [NOTHING | ... ] expression */
result = makeNode(UpsertExpr);
result->updateTlist = updateTlist;
result->exclRelIndex = exclRelIndex;
result->exclRelTlist = exclRelTlist;
result->upsertAction = action;
result->upsertWhere = updateWhere;
return result;
}
/*
* Prepare an INSERT row for assignment to the target table.
*
* The row might be either a VALUES row, or variables referencing a
* sub-SELECT output.
*/
List* transformInsertRow(ParseState* pstate, List* exprlist, List* stmtcols, List* icolumns, List* attrnos)
{
List* result = NIL;
ListCell* lc = NULL;
ListCell* icols = NULL;
ListCell* attnos = NULL;
/*
* Check length of expr list. It must not have more expressions than
* there are target columns. We allow fewer, but only if no explicit
* columns list was given (the remaining columns are implicitly
* defaulted). Note we must check this *after* transformation because
* that could expand '*' into multiple items.
*/
if (list_length(exprlist) > list_length(icolumns)) {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("INSERT has more expressions than target columns"),
parser_errposition(pstate, exprLocation((Node*)list_nth(exprlist, list_length(icolumns))))));
}
if (stmtcols != NIL && list_length(exprlist) < list_length(icolumns)) {
/*
* We can get here for cases like INSERT ... SELECT (a,b,c) FROM ...
* where the user accidentally created a RowExpr instead of separate
* columns. Add a suitable hint if that seems to be the problem,
* because the main error message is quite misleading for this case.
* (If there's no stmtcols, you'll get something about data type
* mismatch, which is less misleading so we don't worry about giving a
* hint in that case.)
*/
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("INSERT has more target columns than expressions"),
((list_length(exprlist) == 1 &&
count_rowexpr_columns(pstate, (Node*)linitial(exprlist)) == list_length(icolumns))
? errhint("The insertion source is a row expression containing the same number of columns "
"expected by the INSERT. Did you accidentally use extra parentheses?")
: 0),
parser_errposition(pstate, exprLocation((Node*)list_nth(icolumns, list_length(exprlist))))));
}
/*
* Prepare columns for assignment to target table.
*/
result = NIL;
icols = list_head(icolumns);
attnos = list_head(attrnos);
foreach (lc, exprlist) {
Expr* expr = (Expr*)lfirst(lc);
ResTarget* col = NULL;
#ifndef ENABLE_MULTIPLE_NODES
/*
* Rownum is not allowed in exprlist in INSERT statement.
*/
ExcludeRownumExpr(pstate, (Node*)expr);
#endif
col = (ResTarget*)lfirst(icols);
AssertEreport(IsA(col, ResTarget), MOD_OPT, "nodeType inconsistant");
expr = transformAssignedExpr(pstate, expr, EXPR_KIND_INSERT_TARGET, col->name, lfirst_int(attnos), col->indirection, col->location,
(Relation)linitial(pstate->p_target_relation), (RangeTblEntry*)linitial(pstate->p_target_rangetblentry));
result = lappend(result, expr);
icols = lnext(icols);
attnos = lnext(attnos);
}
return result;
}
/*
* count_rowexpr_columns -
* get number of columns contained in a ROW() expression;
* return -1 if expression isn't a RowExpr or a Var referencing one.
*
* This is currently used only for hint purposes, so we aren't terribly
* tense about recognizing all possible cases. The Var case is interesting
* because that's what we'll get in the INSERT ... SELECT (...) case.
*/
static int count_rowexpr_columns(ParseState* pstate, Node* expr)
{
if (expr == NULL) {
return -1;
}
if (IsA(expr, RowExpr)) {
return list_length(((RowExpr*)expr)->args);
}
if (IsA(expr, Var)) {
Var* var = (Var*)expr;
AttrNumber attnum = var->varattno;
if (attnum > 0 && var->vartype == RECORDOID) {
RangeTblEntry* rte = NULL;
rte = GetRTEByRangeTablePosn(pstate, var->varno, var->varlevelsup);
if (rte->rtekind == RTE_SUBQUERY) {
/* Subselect-in-FROM: examine sub-select's output expr */
TargetEntry* ste = get_tle_by_resno(rte->subquery->targetList, attnum);
if (ste == NULL || ste->resjunk) {
return -1;
}
expr = (Node*)ste->expr;
if (IsA(expr, RowExpr)) {
return list_length(((RowExpr*)expr)->args);
}
}
}
}
return -1;
}
static char* fetchSelectStmtFromCTAS(char* source_text)
{
char* select_pos = strcasestr(source_text, "SELECT");
return select_pos;
}
static bool shouldTransformStartWithStmt(ParseState* pstate, SelectStmt* stmt, Query* selectQuery)
{
/*
* Only applicable to START WITH CONNECT BY clauses.
*/
if (stmt->startWithClause == NULL || pstate->p_sourcetext == NULL) {
return false;
}
/*
* Back up the current select statement to be restored after query re-writing
* for cases of START WITH CONNNECT BY under CREATE TABLE AS.
*/
selectQuery->sql_statement = fetchSelectStmtFromCTAS((char*)pstate->p_sourcetext);
return true;
}
/*
* transformVariableSetValueStmt -
* transforms a VariableSetStmt
*/
static Query* transformVariableMutiSetStmt(ParseState* pstate, VariableMultiSetStmt* muti_stmt)
{
Query *query = makeNode(Query);
List *resultList = NIL;
List* stmts = muti_stmt->args;
ListCell* cell = NULL;
VariableSetStmt* set_stmt;
List *usersetlist = NIL;
foreach(cell, stmts) {
Node* stmt = (Node*)lfirst(cell);
if (nodeTag(stmt) == T_AlterSystemStmt) {
AlterSystemStmt *alter_sys_stmt = (AlterSystemStmt *)stmt;
set_stmt = alter_sys_stmt->setstmt;
} else {
set_stmt = (VariableSetStmt*)stmt;
}
if (set_stmt->kind == VAR_SET_DEFINED) {
transformVariableSetStmt(pstate, set_stmt);
if (strcmp(set_stmt->name, "USER DEFINED VARIABLE") == 0) {
usersetlist = list_concat(usersetlist, set_stmt->defined_args);
}
}
if (set_stmt->kind == VAR_SET_VALUE)
transformVariableSetValueStmt(pstate, set_stmt);
if (nodeTag(stmt) == T_AlterSystemStmt) {
AlterSystemStmt *newnode = makeNode(AlterSystemStmt);
newnode->setstmt = set_stmt;
resultList = lappend(resultList, newnode);
} else if (set_stmt->kind == VAR_SET_DEFINED) {
if (strcmp(set_stmt->name, "USER DEFINED VARIABLE") != 0) {
resultList = lappend(resultList, set_stmt);
}
} else {
resultList = lappend(resultList, set_stmt);
}
}
if (list_length(usersetlist) != 0) {
VariableSetStmt* new_set_stmt = makeNode(VariableSetStmt);
new_set_stmt->kind = VAR_SET_DEFINED;
new_set_stmt->name = "USER DEFINED VARIABLE";
new_set_stmt->is_local = false;
new_set_stmt->defined_args = usersetlist;
resultList = lappend(resultList, new_set_stmt);
}
list_free(muti_stmt->args);
muti_stmt->args = resultList;
query->commandType = CMD_UTILITY;
query->utilityStmt = (Node*)muti_stmt;
return query;
}
/*
* transformVariableSetStmt - transforms a user-defined variable
*/
static void transformVariableSetStmt(ParseState* pstate, VariableSetStmt* stmt)
{
List *resultList = NIL;
ListCell *temp = NULL;
foreach (temp, stmt->defined_args) {
UserSetElem *userElem = (UserSetElem *)lfirst(temp);
UserSetElem *newUserElem = makeNode(UserSetElem);
newUserElem->name = userElem->name;
Node *node = transformExprRecurse(pstate, (Node *)userElem->val);
if (IsA(node, UserSetElem)) {
newUserElem->name = list_concat(newUserElem->name, ((UserSetElem *)node)->name);
newUserElem->val = ((UserSetElem *)node)->val;
} else {
newUserElem->val = (Expr *)node;
}
resultList = lappend(resultList, newUserElem);
}
stmt->defined_args = resultList;
}
/*
* transformVariableSetValueStmt -
* transforms a VariableSetStmt
*/
static void transformVariableSetValueStmt(ParseState* pstate, VariableSetStmt* stmt)
{
List *resultlist = NIL;
ListCell* l = NULL;
foreach(l, stmt->args) {
Node* expr = (Node*)lfirst(l);
/*
* The expression will eventually be converted to A_Const,
* so the current A_Const expr does not need to be converted.
*/
Node *node = NULL;
if (IsA(expr, A_Const)) {
node = expr;
} else {
node = transformExpr(pstate, expr, EXPR_KIND_OTHER);
}
resultlist = lappend(resultlist, (Expr*)node);
}
list_free(stmt->args);
stmt->args = resultlist;
}
/*
* transformSelectStmt -
* transforms a Select Statement
*
* Note: this covers only cases with no set operations and no VALUES lists;
* see below for the other cases.
*/
static Query* transformSelectStmt(ParseState* pstate, SelectStmt* stmt, bool isFirstNode, bool isCreateView)
{
Query* qry = makeNode(Query);
Node* qual = NULL;
ListCell* l = NULL;
qry->commandType = CMD_SELECT;
if (stmt->startWithClause != NULL) {
pstate->p_addStartInfo = true;
pstate->p_sw_selectstmt = stmt;
pstate->origin_with = (WithClause *)copyObject(stmt->withClause);
}
/* process the WITH clause independently of all else */
if (stmt->withClause) {
qry->hasRecursive = stmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, stmt->withClause);
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
}
/* Complain if we get called from someplace where INTO is not allowed */
if (stmt->intoClause) {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("SELECT ... INTO is not allowed here"),
parser_errposition(pstate, exprLocation((Node*)stmt->intoClause))));
}
/* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
pstate->p_locking_clause = stmt->lockingClause;
/* make WINDOW info available for window functions, too */
pstate->p_windowdefs = stmt->windowClause;
/* process the FROM clause */
transformFromClause(pstate, stmt->fromClause, isFirstNode, isCreateView);
/* process index_hint in tables*/
qry->indexhintList = lappend3(qry->indexhintList, pstate->p_indexhintLists);
/* transform START WITH...CONNECT BY clause */
if (shouldTransformStartWithStmt(pstate, stmt, qry)) {
transformStartWith(pstate, stmt, qry);
}
/* transform targetlist */
qry->targetList = transformTargetList(pstate, stmt->targetList, EXPR_KIND_SELECT_TARGET);
/* Transform operator "(+)" to outer join */
if (stmt->hasPlus && stmt->whereClause != NULL) {
transformOperatorPlus(pstate, &stmt->whereClause);
}
qry->starStart = list_copy(pstate->p_star_start);
qry->starEnd = list_copy(pstate->p_star_end);
qry->starOnly = list_copy(pstate->p_star_only);
/* mark column origins */
markTargetListOrigins(pstate, qry->targetList);
/* transform WHERE
* Only "(+)" is valid when it's in WhereClause of Select, set the flag to be trure
* during transform Whereclause.
*/
setIgnorePlusFlag(pstate, true);
qual = transformWhereClause(pstate, stmt->whereClause, EXPR_KIND_WHERE, "WHERE");
setIgnorePlusFlag(pstate, false);
/*
* Initial processing of HAVING clause is just like WHERE clause.
*/
qry->havingQual = transformWhereClause(pstate, stmt->havingClause, EXPR_KIND_HAVING, "HAVING");
pstate->shouldCheckOrderbyCol = (!ALLOW_ORDERBY_UNDISTINCT_COLUMN &&
stmt->distinctClause && linitial(stmt->distinctClause) == NULL &&
!IsInitdb && DB_IS_CMPT(B_FORMAT));
/*
* Transform sorting/grouping stuff. Do ORDER BY first because both
* transformGroupClause and transformDistinctClause need the results. Note
* that these functions can also change the targetList, so it's passed to
* them by reference.
*/
qry->sortClause = transformSortClause(
pstate, stmt->sortClause, &qry->targetList, EXPR_KIND_ORDER_BY, true /* fix unknowns */, false /* allow SQL92 rules */);
pstate->shouldCheckOrderbyCol = false;
/*
* Transform A_const to columnref type in group by clause, So that repeated group column
* will deleted in function transformGroupClause. If not to delete repeated column, for
* group by rollup can have error result, because we need set null to non- group column.
*
* select a, b, b
* from t1
* group by rollup(1, 2), 3;
*
* To this example, column b should not be set to null, but if not to delete repeated column
* b will be set to null and two b value is not equal.
*/
if (include_groupingset((Node*)stmt->groupClause)) {
transformGroupConstToColumn(pstate, (Node*)stmt->groupClause, qry->targetList);
}
qry->groupClause = transformGroupClause(pstate,
stmt->groupClause,
&qry->groupingSets,
&qry->targetList,
qry->sortClause,
EXPR_KIND_GROUP_BY,
false /* allow SQL92 rules */);
if (stmt->distinctClause == NIL) {
qry->distinctClause = NIL;
qry->hasDistinctOn = false;
} else if (linitial(stmt->distinctClause) == NULL) {
/* We had SELECT DISTINCT */
qry->distinctClause = transformDistinctClause(pstate, &qry->targetList, qry->sortClause, false);
qry->hasDistinctOn = false;
} else {
/* We had SELECT DISTINCT ON */
qry->distinctClause =
transformDistinctOnClause(pstate, stmt->distinctClause, &qry->targetList, qry->sortClause);
qry->hasDistinctOn = true;
}
/* transform LIMIT */
qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset, EXPR_KIND_OFFSET, "OFFSET");
qry->limitCount = transformLimitClause(pstate, stmt->limitCount, EXPR_KIND_LIMIT, "LIMIT");
/* transform window clauses after we have seen all window functions */
qry->windowClause = transformWindowDefinitions(pstate, pstate->p_windowdefs, &qry->targetList);
/* resolve any still-unresolved output columns as being type text */
if (pstate->p_resolve_unknowns) {
resolveTargetListUnknowns(pstate, qry->targetList);
}
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
if (pstate->p_hasWindowFuncs) {
parseCheckWindowFuncs(pstate, qry);
}
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
qry->hasAggs = pstate->p_hasAggs;
foreach (l, stmt->lockingClause) {
transformLockingClause(pstate, qry, (LockingClause*)lfirst(l), false);
}
qry->hintState = stmt->hintState;
/*
* If query is under one insert statement and include a foreign table,
* then set top level parsestate p_is_foreignTbl_exist to true.
*/
if (u_sess->attr.attr_sql.td_compatible_truncation && u_sess->attr.attr_sql.sql_compatibility == C_FORMAT &&
pstate->p_is_in_insert && checkForeignTableExist(pstate->p_rtable))
set_ancestor_ps_contain_foreigntbl(pstate);
assign_query_collations(pstate, qry);
/* this must be done after collations, for reliable comparison of exprs */
if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual) {
parseCheckAggregates(pstate, qry);
}
/*
* If SelectStmt has been rewrite by startwith/connectby, it should
* return as With-Recursive for upper level. So we need to fix fromClause.
*/
if (pstate->p_addStartInfo) {
AdaptSWSelectStmt(pstate, stmt);
}
return qry;
}
/*
* transformValuesClause -
* transforms a VALUES clause that's being used as a standalone SELECT
*
* We build a Query containing a VALUES RTE, rather as if one had written
* SELECT * FROM (VALUES ...) AS "*VALUES*"
*/
static Query* transformValuesClause(ParseState* pstate, SelectStmt* stmt)
{
Query* qry = makeNode(Query);
List* exprsLists = NIL;
List* collations = NIL;
List** colexprs = NULL;
int sublist_length = -1;
RangeTblEntry* rte = NULL;
int rtindex;
ListCell* lc = NULL;
ListCell* lc2 = NULL;
int i;
qry->commandType = CMD_SELECT;
/* Most SELECT stuff doesn't apply in a VALUES clause */
AssertEreport(stmt->distinctClause == NIL, MOD_OPT, "distinctClause should not happen here");
AssertEreport(stmt->intoClause == NULL, MOD_OPT, "intoClause should not happen here");
AssertEreport(stmt->targetList == NIL, MOD_OPT, "targetList only accept NIL here");
AssertEreport(stmt->fromClause == NIL, MOD_OPT, "fromClause should not happen here");
AssertEreport(stmt->whereClause == NULL, MOD_OPT, "whereClause should not happen here");
AssertEreport(stmt->groupClause == NIL, MOD_OPT, "groupClause should not happen here");
AssertEreport(stmt->havingClause == NULL, MOD_OPT, "havingClause should not happen here");
AssertEreport(stmt->windowClause == NIL, MOD_OPT, "windowClause should not happen here");
AssertEreport(stmt->op == SETOP_NONE, MOD_OPT, "SetOperation type is inconsistant");
/* process the WITH clause independently of all else */
if (stmt->withClause) {
qry->hasRecursive = stmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, stmt->withClause);
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
}
/*
* For each row of VALUES, transform the raw expressions. This is also a
* handy place to reject DEFAULT nodes and Rownum, which the grammar allows for
* simplicity.
*
* Note that the intermediate representation we build is column-organized
* not row-organized. That simplifies the type and collation processing
* below.
*/
foreach (lc, stmt->valuesLists) {
List* sublist = (List*)lfirst(lc);
/* Do basic expression transformation (same as a ROW() expr) */
sublist = transformExpressionList(pstate, sublist, EXPR_KIND_VALUES);
/*
* All the sublists must be the same length, *after* transformation
* (which might expand '*' into multiple items). The VALUES RTE can't
* handle anything different.
*/
if (sublist_length < 0) {
/* Remember post-transformation length of first sublist */
sublist_length = list_length(sublist);
/* and allocate array for per-column lists */
colexprs = (List**)palloc0(sublist_length * sizeof(List*));
} else if (sublist_length != list_length(sublist)) {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("VALUES lists must all be the same length"),
parser_errposition(pstate, exprLocation((Node*)sublist))));
}
/* Check for DEFAULT and Rownum, then build per-column expression lists */
i = 0;
foreach (lc2, sublist) {
Node* col = (Node*)lfirst(lc2);
if (IsA(col, SetToDefault)) {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("DEFAULT can only appear in a VALUES list within INSERT"),
parser_errposition(pstate, exprLocation(col))));
}
#ifndef ENABLE_MULTIPLE_NODES
ExcludeRownumExpr(pstate, col);
#endif
colexprs[i] = lappend(colexprs[i], col);
i++;
}
/* Release sub-list's cells to save memory */
list_free_ext(sublist);
}
/*
* Now resolve the common types of the columns, and coerce everything to
* those types. Then identify the common collation, if any, of each
* column.
*
* We must do collation processing now because (1) assign_query_collations
* doesn't process rangetable entries, and (2) we need to label the VALUES
* RTE with column collations for use in the outer query. We don't
* consider conflict of implicit collations to be an error here; instead
* the column will just show InvalidOid as its collation, and you'll get a
* failure later if that results in failure to resolve a collation.
*
* Note we modify the per-column expression lists in-place.
*/
collations = NIL;
for (i = 0; i < sublist_length; i++) {
Oid coltype;
Oid colcoll;
coltype = select_common_type(pstate, colexprs[i], "VALUES", NULL);
foreach (lc, colexprs[i]) {
Node* col = (Node*)lfirst(lc);
col = coerce_to_common_type(pstate, col, coltype, "VALUES");
lfirst(lc) = (void*)col;
}
colcoll = select_common_collation(pstate, colexprs[i], true);
collations = lappend_oid(collations, colcoll);
}
/*
* Finally, rearrange the coerced expressions into row-organized lists.
*/
exprsLists = NIL;
foreach (lc, colexprs[0]) {
Node* col = (Node*)lfirst(lc);
List* sublist = NIL;
sublist = list_make1(col);
exprsLists = lappend(exprsLists, sublist);
}
list_free_ext(colexprs[0]);
for (i = 1; i < sublist_length; i++) {
forboth(lc, colexprs[i], lc2, exprsLists)
{
Node* col = (Node*)lfirst(lc);
List* sublist = (List*)lfirst(lc2);
/* sublist pointer in exprsLists won't need adjustment */
(void)lappend(sublist, col);
}
list_free_ext(colexprs[i]);
}
/*
* Generate the VALUES RTE
*/
rte = addRangeTableEntryForValues(pstate, exprsLists, collations, NULL, true);
addRTEtoQuery(pstate, rte, true, true, true);
/* assume new rte is at end */
rtindex = list_length(pstate->p_rtable);
if (rte != rt_fetch(rtindex, pstate->p_rtable)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("rte is not in p_rtable list")));
}
/*
* Generate a targetlist as though expanding "*"
*/
AssertEreport(pstate->p_next_resno == 1, MOD_OPT, "");
qry->targetList = expandRelAttrs(pstate, rte, rtindex, 0, -1);
/*
* The grammar allows attaching ORDER BY, LIMIT, and FOR UPDATE to a
* VALUES, so cope.
*/
qry->sortClause = transformSortClause(
pstate, stmt->sortClause, &qry->targetList, EXPR_KIND_ORDER_BY, true /* fix unknowns */, false /* allow SQL92 rules */);
qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset, EXPR_KIND_OFFSET, "OFFSET");
qry->limitCount = transformLimitClause(pstate, stmt->limitCount, EXPR_KIND_LIMIT, "LIMIT");
if (stmt->lockingClause) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES")));
}
/*
* There mustn't have been any table references in the expressions, else
* strange things would happen, like Cartesian products of those tables
* with the VALUES list. We have to check this after parsing ORDER BY et
* al since those could insert more junk.
*/
if (list_length(pstate->p_joinlist) != 1) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VALUES must not contain table references"),
parser_errposition(pstate, locate_var_of_level((Node*)exprsLists, 0))));
}
if (list_length(pstate->p_rtable) != 1 && contain_vars_of_level((Node*)exprsLists, 0)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VALUES must not contain OLD or NEW references"),
errhint("Use SELECT ... UNION ALL ... instead."),
parser_errposition(pstate, locate_var_of_level((Node*)exprsLists, 0))));
}
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
qry->hasSubLinks = pstate->p_hasSubLinks;
/* aggregates not allowed (but subselects are okay) */
if (pstate->p_hasAggs) {
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in VALUES"),
parser_errposition(pstate, locate_agg_of_level((Node*)exprsLists, 0))));
}
if (pstate->p_hasWindowFuncs) {
ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR),
errmsg("cannot use window function in VALUES"),
parser_errposition(pstate, locate_windowfunc((Node*)exprsLists))));
}
assign_query_collations(pstate, qry);
return qry;
}
/*
* transformSetOperationStmt -
* transforms a set-operations tree
*
* A set-operation tree is just a SELECT, but with UNION/INTERSECT/EXCEPT
* structure to it. We must transform each leaf SELECT and build up a top-
* level Query that contains the leaf SELECTs as subqueries in its rangetable.
* The tree of set operations is converted into the setOperations field of
* the top-level Query.
*/
static Query* transformSetOperationStmt(ParseState* pstate, SelectStmt* stmt)
{
Query* qry = makeNode(Query);
SelectStmt* leftmostSelect = NULL;
int leftmostRTI;
Query* leftmostQuery = NULL;
SetOperationStmt* sostmt = NULL;
List* sortClause = NIL;
Node* limitOffset = NULL;
Node* limitCount = NULL;
List* lockingClause = NIL;
WithClause* withClause = NULL;
Node* node = NULL;
ListCell* left_tlist = NULL;
ListCell* lct = NULL;
ListCell* lcm = NULL;
ListCell* lcc = NULL;
ListCell* l = NULL;
List* targetvars = NIL;
List* targetnames = NIL;
List* sv_relnamespace = NIL;
List* sv_varnamespace = NIL;
int sv_rtable_length;
RangeTblEntry* jrte = NULL;
int tllen;
qry->commandType = CMD_SELECT;
/*
* Find leftmost leaf SelectStmt. We currently only need to do this in
* order to deliver a suitable error message if there's an INTO clause
* there, implying the set-op tree is in a context that doesn't allow
* INTO. (transformSetOperationTree would throw error anyway, but it
* seems worth the trouble to throw a different error for non-leftmost
* INTO, so we produce that error in transformSetOperationTree.)
*/
leftmostSelect = stmt->larg;
while (leftmostSelect != NULL && leftmostSelect->op != SETOP_NONE) {
leftmostSelect = leftmostSelect->larg;
}
AssertEreport(leftmostSelect && IsA(leftmostSelect, SelectStmt) && leftmostSelect->larg == NULL, MOD_OPT, "");
if (leftmostSelect->intoClause) {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("SELECT ... INTO is not allowed here"),
parser_errposition(pstate, exprLocation((Node*)leftmostSelect->intoClause))));
}
/*
* We need to extract ORDER BY and other top-level clauses here and not
* let transformSetOperationTree() see them --- else it'll just recurse
* right back here!
*/
sortClause = stmt->sortClause;
limitOffset = stmt->limitOffset;
limitCount = stmt->limitCount;
lockingClause = stmt->lockingClause;
withClause = stmt->withClause;
stmt->sortClause = NIL;
stmt->limitOffset = NULL;
stmt->limitCount = NULL;
stmt->lockingClause = NIL;
stmt->withClause = NULL;
/* We don't support FOR UPDATE/SHARE with set ops at the moment. */
if (lockingClause != NULL) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
}
/* Process the WITH clause independently of all else */
if (withClause != NULL) {
qry->hasRecursive = withClause->recursive;
qry->cteList = transformWithClause(pstate, withClause);
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
}
/*
* Recursively transform the components of the tree.
*/
sostmt = (SetOperationStmt*)transformSetOperationTree(pstate, stmt, true, NULL);
AssertEreport(
sostmt && IsA(sostmt, SetOperationStmt), MOD_OPT, "Recursively transform the components of the tree failure");
qry->setOperations = (Node*)sostmt;
/*
* Re-find leftmost SELECT (now it's a sub-query in rangetable)
*/
node = sostmt->larg;
while (node && IsA(node, SetOperationStmt))
node = ((SetOperationStmt*)node)->larg;
AssertEreport(node && IsA(node, RangeTblRef), MOD_OPT, "Node type inconsistant");
leftmostRTI = ((RangeTblRef*)node)->rtindex;
leftmostQuery = rt_fetch(leftmostRTI, pstate->p_rtable)->subquery;
AssertEreport(leftmostQuery != NULL, MOD_OPT, "para should not be NULL");
/*
* Generate dummy targetlist for outer query using column names of
* leftmost select and common datatypes/collations of topmost set
* operation. Also make lists of the dummy vars and their names for use
* in parsing ORDER BY.
*
* Note: we use leftmostRTI as the varno of the dummy variables. It
* shouldn't matter too much which RT index they have, as long as they
* have one that corresponds to a real RT entry; else funny things may
* happen when the tree is mashed by rule rewriting.
*/
qry->targetList = NIL;
targetvars = NIL;
targetnames = NIL;
left_tlist = list_head(leftmostQuery->targetList);
forthree(lct, sostmt->colTypes, lcm, sostmt->colTypmods, lcc, sostmt->colCollations)
{
Oid colType = lfirst_oid(lct);
int32 colTypmod = lfirst_int(lcm);
Oid colCollation = lfirst_oid(lcc);
TargetEntry* lefttle = (TargetEntry*)lfirst(left_tlist);
char* colName = NULL;
TargetEntry* tle = NULL;
Var* var = NULL;
AssertEreport(!lefttle->resjunk, MOD_OPT, "");
colName = pstrdup(lefttle->resname);
var = makeVar(leftmostRTI, lefttle->resno, colType, colTypmod, colCollation, 0);
var->location = exprLocation((Node*)lefttle->expr);
tle = makeTargetEntry((Expr*)var, (AttrNumber)pstate->p_next_resno++, colName, false);
qry->targetList = lappend(qry->targetList, tle);
targetvars = lappend(targetvars, var);
targetnames = lappend(targetnames, makeString(colName));
left_tlist = lnext(left_tlist);
}
/*
* As a first step towards supporting sort clauses that are expressions
* using the output columns, generate a varnamespace entry that makes the
* output columns visible. A Join RTE node is handy for this, since we
* can easily control the Vars generated upon matches.
*
* Note: we don't yet do anything useful with such cases, but at least
* "ORDER BY upper(foo)" will draw the right error message rather than
* "foo not found".
*/
sv_rtable_length = list_length(pstate->p_rtable);
jrte = addRangeTableEntryForJoin(pstate, targetnames, JOIN_INNER, targetvars, NULL, false);
sv_relnamespace = pstate->p_relnamespace;
sv_varnamespace = pstate->p_varnamespace;
pstate->p_relnamespace = NIL;
pstate->p_varnamespace = NIL;
/* add jrte to varnamespace only */
addRTEtoQuery(pstate, jrte, false, false, true);
/*
* For now, we don't support resjunk sort clauses on the output of a
* setOperation tree --- you can only use the SQL92-spec options of
* selecting an output column by name or number. Enforce by checking that
* transformSortClause doesn't add any items to tlist.
*/
tllen = list_length(qry->targetList);
qry->sortClause = transformSortClause(
pstate, sortClause, &qry->targetList, EXPR_KIND_ORDER_BY, false /* no unknowns expected */, false /* allow SQL92 rules */);
pstate->p_rtable = list_truncate(pstate->p_rtable, sv_rtable_length);
pstate->p_relnamespace = sv_relnamespace;
pstate->p_varnamespace = sv_varnamespace;
if (tllen != list_length(qry->targetList)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("invalid UNION/INTERSECT/EXCEPT ORDER BY clause"),
errdetail("Only result column names can be used, not expressions or functions."),
errhint("Add the expression/function to every SELECT, or move the UNION into a FROM clause."),
parser_errposition(pstate, exprLocation((const Node*)list_nth(qry->targetList, tllen)))));
}
qry->limitOffset = transformLimitClause(pstate, limitOffset, EXPR_KIND_OFFSET, "OFFSET");
qry->limitCount = transformLimitClause(pstate, limitCount, EXPR_KIND_LIMIT, "LIMIT");
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
if (pstate->p_hasWindowFuncs) {
parseCheckWindowFuncs(pstate, qry);
}
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
qry->hasAggs = pstate->p_hasAggs;
foreach (l, lockingClause) {
transformLockingClause(pstate, qry, (LockingClause*)lfirst(l), false);
}
assign_query_collations(pstate, qry);
/* this must be done after collations, for reliable comparison of exprs */
if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual) {
parseCheckAggregates(pstate, qry);
}
return qry;
}
/*
* transformSetOperationTree
* Recursively transform leaves and internal nodes of a set-op tree
*
* In addition to returning the transformed node, if targetlist isn't NULL
* then we return a list of its non-resjunk TargetEntry nodes. For a leaf
* set-op node these are the actual targetlist entries; otherwise they are
* dummy entries created to carry the type, typmod, collation, and location
* (for error messages) of each output column of the set-op node. This info
* is needed only during the internal recursion of this function, so outside
* callers pass NULL for targetlist. Note: the reason for passing the
* actual targetlist entries of a leaf node is so that upper levels can
* replace UNKNOWN Consts with properly-coerced constants.
*/
static Node* transformSetOperationTree(ParseState* pstate, SelectStmt* stmt, bool isTopLevel, List** targetlist)
{
bool isLeaf = false;
int rc = 0;
AssertEreport(stmt && IsA(stmt, SelectStmt), MOD_OPT, "check stmt failure");
/* Guard against stack overflow due to overly complex set-expressions */
check_stack_depth();
/*
* Validity-check both leaf and internal SELECTs for disallowed ops.
*/
if (stmt->intoClause) {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT"),
parser_errposition(pstate, exprLocation((Node*)stmt->intoClause))));
}
/* We don't support FOR UPDATE/SHARE with set ops at the moment. */
if (stmt->lockingClause) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
}
/*
* If an internal node of a set-op tree has ORDER BY, LIMIT, FOR UPDATE,
* or WITH clauses attached, we need to treat it like a leaf node to
* generate an independent sub-Query tree. Otherwise, it can be
* represented by a SetOperationStmt node underneath the parent Query.
*/
if (stmt->op == SETOP_NONE) {
AssertEreport(stmt->larg == NULL && stmt->rarg == NULL, MOD_OPT, "stmt is inconsistant");
isLeaf = true;
} else {
AssertEreport(stmt->larg != NULL && stmt->rarg != NULL, MOD_OPT, "");
if (stmt->sortClause || stmt->limitOffset || stmt->limitCount || stmt->lockingClause || stmt->withClause) {
isLeaf = true;
} else {
isLeaf = false;
}
}
if (isLeaf) {
/* Process leaf SELECT */
Query* selectQuery = NULL;
char selectName[32];
RangeTblEntry PG_USED_FOR_ASSERTS_ONLY* rte = NULL;
RangeTblRef* rtr = NULL;
ListCell* tl = NULL;
/*
* Transform SelectStmt into a Query.
*
* Note: previously transformed sub-queries don't affect the parsing
* of this sub-query, because they are not in the toplevel pstate's
* namespace list.
*/
selectQuery = parse_sub_analyze((Node*)stmt, pstate, NULL, false, false);
/*
* Check for bogus references to Vars on the current query level (but
* upper-level references are okay). Normally this can't happen
* because the namespace will be empty, but it could happen if we are
* inside a rule.
*/
if (pstate->p_relnamespace || pstate->p_varnamespace) {
if (contain_vars_of_level((Node*)selectQuery, 1)) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("UNION/INTERSECT/EXCEPT member statement cannot refer to other relations of same query "
"level"),
parser_errposition(pstate, locate_var_of_level((Node*)selectQuery, 1))));
}
}
/*
* Extract a list of the non-junk TLEs for upper-level processing.
*/
if (targetlist != NULL) {
*targetlist = NIL;
foreach (tl, selectQuery->targetList) {
TargetEntry* tle = (TargetEntry*)lfirst(tl);
if (!tle->resjunk) {
*targetlist = lappend(*targetlist, tle);
}
}
}
/*
* Make the leaf query be a subquery in the top-level rangetable.
*/
rc = snprintf_s(
selectName, sizeof(selectName), sizeof(selectName) - 1, "*SELECT* %d", list_length(pstate->p_rtable) + 1);
securec_check_ss(rc, "", "");
rte = addRangeTableEntryForSubquery(pstate, selectQuery, makeAlias(selectName, NIL), false, false);
/*
* Return a RangeTblRef to replace the SelectStmt in the set-op tree.
*/
rtr = makeNode(RangeTblRef);
/* assume new rte is at end */
rtr->rtindex = list_length(pstate->p_rtable);
AssertEreport(rte == rt_fetch(rtr->rtindex, pstate->p_rtable), MOD_OPT, "check with rt_fetch failure");
return (Node*)rtr;
} else {
/* Process an internal node (set operation node) */
SetOperationStmt* op = makeNode(SetOperationStmt);
List* ltargetlist = NIL;
List* rtargetlist = NIL;
ListCell* ltl = NULL;
ListCell* rtl = NULL;
const char* context = NULL;
context = (stmt->op == SETOP_UNION ? "UNION" : (stmt->op == SETOP_INTERSECT ? "INTERSECT" : "EXCEPT"));
op->op = stmt->op;
op->all = stmt->all;
/*
* Recursively transform the left child node.
*/
op->larg = transformSetOperationTree(pstate, stmt->larg, false, &ltargetlist);
/*
* If we are processing a recursive union query, now is the time to
* examine the non-recursive term's output columns and mark the
* containing CTE as having those result columns. We should do this
* only at the topmost setop of the CTE, of course.
*/
if (isTopLevel && pstate->p_parent_cte && pstate->p_parent_cte->cterecursive) {
determineRecursiveColTypes(pstate, op->larg, ltargetlist);
}
/*
* Recursively transform the right child node.
*/
op->rarg = transformSetOperationTree(pstate, stmt->rarg, false, &rtargetlist);
/*
* Verify that the two children have the same number of non-junk
* columns, and determine the types of the merged output columns.
*/
if (list_length(ltargetlist) != list_length(rtargetlist)) {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("each %s query must have the same number of columns", context),
parser_errposition(pstate, exprLocation((Node*)rtargetlist))));
}
if (targetlist != NULL) {
*targetlist = NIL;
}
op->colTypes = NIL;
op->colTypmods = NIL;
op->colCollations = NIL;
op->groupClauses = NIL;
forboth(ltl, ltargetlist, rtl, rtargetlist)
{
TargetEntry* ltle = (TargetEntry*)lfirst(ltl);
TargetEntry* rtle = (TargetEntry*)lfirst(rtl);
Node* lcolnode = (Node*)ltle->expr;
Node* rcolnode = (Node*)rtle->expr;
Oid lcoltype = exprType(lcolnode);
Oid rcoltype = exprType(rcolnode);
int32 lcoltypmod = exprTypmod(lcolnode);
int32 rcoltypmod = exprTypmod(rcolnode);
Node* bestexpr = NULL;
int bestlocation;
Oid rescoltype;
int32 rescoltypmod;
Oid rescolcoll;
if (IsA(lcolnode, SetToDefault) && ((SetToDefault*)lcolnode)->lrchild_unknown && rcoltype != UNKNOWNOID) {
/* the type of lcolnode's l&r child must be unknown or NULL, and lcoltype must be text. so allow text cover to other type */
rescoltype = rcoltype;
bestexpr = rcolnode;
} else if (IsA(rcolnode, SetToDefault) && ((SetToDefault*)rcolnode)->lrchild_unknown && lcoltype != UNKNOWNOID) {
rescoltype = lcoltype;
bestexpr = lcolnode;
} else {
/* select common type, same as CASE et al */
rescoltype = select_common_type(pstate, list_make2(lcolnode, rcolnode), context, &bestexpr);
}
bestlocation = exprLocation(bestexpr);
/* if same type and same typmod, use typmod; else default */
if (lcoltype == rcoltype && lcoltypmod == rcoltypmod) {
rescoltypmod = lcoltypmod;
} else {
rescoltypmod = -1;
}
/*
* Verify the coercions are actually possible. If not, we'd fail
* later anyway, but we want to fail now while we have sufficient
* context to produce an error cursor position.
*
* For all non-UNKNOWN-type cases, we verify coercibility but we
* don't modify the child's expression, for fear of changing the
* child query's semantics.
*
* If a child expression is an UNKNOWN-type Const or Param, we
* want to replace it with the coerced expression. This can only
* happen when the child is a leaf set-op node. It's safe to
* replace the expression because if the child query's semantics
* depended on the type of this output column, it'd have already
* coerced the UNKNOWN to something else. We want to do this
* because (a) we want to verify that a Const is valid for the
* target type, or resolve the actual type of an UNKNOWN Param,
* and (b) we want to avoid unnecessary discrepancies between the
* output type of the child query and the resolved target type.
* Such a discrepancy would disable optimization in the planner.
*
* If it's some other UNKNOWN-type node, eg a Var, we do nothing
* (knowing that coerce_to_common_type would fail). The planner
* is sometimes able to fold an UNKNOWN Var to a constant before
* it has to coerce the type, so failing now would just break
* cases that might work.
*/
if (lcoltype != UNKNOWNOID) {
lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype, context);
} else if (IsA(lcolnode, Const) || IsA(lcolnode, Param)) {
lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype, context);
ltle->expr = (Expr*)lcolnode;
}
if (rcoltype != UNKNOWNOID) {
rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype, context);
} else if (IsA(rcolnode, Const) || IsA(rcolnode, Param)) {
rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype, context);
rtle->expr = (Expr*)rcolnode;
}
/*
* Select common collation. A common collation is required for
* all set operators except UNION ALL; see SQL:2008 7.13 <query
* expression> Syntax Rule 15c. (If we fail to identify a common
* collation for a UNION ALL column, the curCollations element
* will be set to InvalidOid, which may result in a runtime error
* if something at a higher query level wants to use the column's
* collation.)
*/
rescolcoll =
select_common_collation(pstate, list_make2(lcolnode, rcolnode), (op->op == SETOP_UNION && op->all));
/* emit results */
op->colTypes = lappend_oid(op->colTypes, rescoltype);
op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
op->colCollations = lappend_oid(op->colCollations, rescolcoll);
/*
* For all cases except UNION ALL, identify the grouping operators
* (and, if available, sorting operators) that will be used to
* eliminate duplicates.
*/
if (op->op != SETOP_UNION || !op->all) {
SortGroupClause* grpcl = makeNode(SortGroupClause);
Oid sortop;
Oid eqop;
bool hashable = false;
ParseCallbackState pcbstate;
setup_parser_errposition_callback(&pcbstate, pstate, bestlocation);
/* determine the eqop and optional sortop */
get_sort_group_operators(rescoltype, false, true, false, &sortop, &eqop, NULL, &hashable);
cancel_parser_errposition_callback(&pcbstate);
/* we don't have a tlist yet, so can't assign sortgrouprefs */
grpcl->tleSortGroupRef = 0;
grpcl->eqop = eqop;
grpcl->sortop = sortop;
grpcl->nulls_first = false; /* OK with or without sortop */
grpcl->hashable = hashable;
op->groupClauses = lappend(op->groupClauses, grpcl);
}
/*
* Construct a dummy tlist entry to return. We use a SetToDefault
* node for the expression, since it carries exactly the fields
* needed, but any other expression node type would do as well.
*/
if (targetlist != NULL) {
SetToDefault* rescolnode = makeNode(SetToDefault);
TargetEntry* restle = NULL;
rescolnode->typeId = rescoltype;
rescolnode->typeMod = rescoltypmod;
rescolnode->collation = rescolcoll;
rescolnode->location = bestlocation;
rescolnode->lrchild_unknown = false;
if ((lcoltype == UNKNOWNOID || (IsA(ltle->expr, SetToDefault) && ((SetToDefault*)ltle->expr)->lrchild_unknown))
&& (rcoltype == UNKNOWNOID || (IsA(rtle->expr, SetToDefault) && ((SetToDefault*)rtle->expr)->lrchild_unknown))) {
rescolnode->lrchild_unknown = true;
}
restle = makeTargetEntry((Expr*)rescolnode,
0, /* no need to set resno */
NULL,
false);
*targetlist = lappend(*targetlist, restle);
}
}
return (Node*)op;
}
}
/*
* Process the outputs of the non-recursive term of a recursive union
* to set up the parent CTE's columns
*/
static void determineRecursiveColTypes(ParseState* pstate, Node* larg, List* nrtargetlist)
{
Node* node = NULL;
int leftmostRTI;
Query* leftmostQuery = NULL;
List* targetList = NIL;
ListCell* left_tlist = NULL;
ListCell* nrtl = NULL;
int next_resno;
/*
* Find leftmost leaf SELECT
*/
node = larg;
while (node && IsA(node, SetOperationStmt)) {
node = ((SetOperationStmt*)node)->larg;
}
AssertEreport(node && IsA(node, RangeTblRef), MOD_OPT, "check node type inconsistant");
leftmostRTI = ((RangeTblRef*)node)->rtindex;
leftmostQuery = rt_fetch(leftmostRTI, pstate->p_rtable)->subquery;
if (unlikely(leftmostQuery == NULL)) {
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("leftmostQuery should not be null")));
}
/*
* Generate dummy targetlist using column names of leftmost select and
* dummy result expressions of the non-recursive term.
*/
targetList = NIL;
left_tlist = list_head(leftmostQuery->targetList);
next_resno = 1;
foreach (nrtl, nrtargetlist) {
TargetEntry* nrtle = (TargetEntry*)lfirst(nrtl);
TargetEntry* lefttle = (TargetEntry*)lfirst(left_tlist);
char* colName = NULL;
TargetEntry* tle = NULL;
AssertEreport(!lefttle->resjunk, MOD_OPT, "");
colName = pstrdup(lefttle->resname);
tle = makeTargetEntry(nrtle->expr, next_resno++, colName, false);
targetList = lappend(targetList, tle);
left_tlist = lnext(left_tlist);
}
/* Now build CTE's output column info using dummy targetlist */
analyzeCTETargetList(pstate, pstate->p_parent_cte, targetList);
}
/*
* fixResTargetNameWithTableNameRef
* The goal of this function try to handle set_clause in updatestmt, and update
* with table name or alias prefix. Do this for compatibility A.
*/
void fixResTargetNameWithTableNameRef(Relation rd, RangeVar* rel, ResTarget* res)
{
char* resname = NULL;
AttrNumber attrnoIndName = InvalidAttrNumber;
AttrNumber attrnoResName = InvalidAttrNumber;
Oid attrtypeid = InvalidOid;
Oid typrelid = InvalidOid;
/* the length of indireciton can't less than 1 and the first item type must not be A_Indices */
if ((list_length(res->indirection) < 1) || IsA(linitial(res->indirection), A_Indices)) {
return;
}
/* name must relname or aliasname */
if ((strcmp(rel->relname, res->name) != 0) &&
((rel->alias == NULL) || (strcmp((rel->alias)->aliasname, res->name) != 0))) {
return;
}
/*
* if meet set_clause like a.b.c = 1 then the indirection will exceed one,
* so treat first indirection as tablename/alisename like a in this ex.
*/
resname = pstrdup(strVal(linitial(res->indirection)));
if (list_length(res->indirection) > 1) {
res->name = resname;
res->indirection = RemoveListCell(res->indirection, 1);
return;
}
/*
* Here only meet set_clause which has two elements like a.b or test.b.
* So first keep forward-compatibility behavior of old version, that results
* if a is a composite type and also relname/alisename then we first handle it as
* composite type.
* Otherwise we handle it like a.b like relname/alisename, then a is a relname just
* update set_clause over.
*/
attrnoResName = attnameAttNum(rd, res->name, true);
attrnoIndName = attnameAttNum(rd, resname, true);
if (attrnoResName > 0) {
attrtypeid = attnumTypeId(rd, attrnoResName);
if (attrtypeid) {
typrelid = typeidTypeRelid(attrtypeid);
if (typrelid && ((get_attnum(typrelid, resname) != InvalidAttrNumber) || (attrnoIndName <= 0))) {
ereport(
NOTICE, (errmsg("update field '%s' of column '%s', though it's ambiguous.", resname, res->name)));
return;
}
}
}
res->name = resname;
res->indirection = RemoveListCell(res->indirection, 1);
}
/*
* fixResTargetListWithTableNameRef
* transforms an update set_clause_list with tablename or alias prefix
*/
void fixResTargetListWithTableNameRef(Relation rd, RangeVar* rel, List* clause_list)
{
ListCell* cell = NULL;
foreach (cell, clause_list) {
if (IsA(lfirst(cell), ResTarget)) {
fixResTargetNameWithTableNameRef(rd, rel, (ResTarget*)lfirst(cell));
}
}
}
/* Merge the targetlists of multiple identical result relations into the one and change their rtindex. */
static void MergeTargetList(List** targetLists, RangeTblEntry* rte1, int rtindex1,
RangeTblEntry* rte2, int rtindex2)
{
ListCell* l = NULL;
foreach (l, targetLists[rtindex2 - 1]) {
TargetEntry* tle = (TargetEntry*)lfirst(l);
tle->rtindex = (Index)rtindex1;
rte1->updatedCols = bms_add_member(rte1->updatedCols, tle->resno - FirstLowInvalidHeapAttributeNumber);
rte2->updatedCols = bms_del_member(rte2->updatedCols, tle->resno - FirstLowInvalidHeapAttributeNumber);
}
targetLists[rtindex1 - 1] = list_concat(targetLists[rtindex1 - 1], targetLists[rtindex2 - 1]);
targetLists[rtindex2 - 1] = NULL;
}
static void transformMultiTargetList(List* target_rangetblentry, List** targetLists)
{
int rtindex1 = 1, rtindex2 = 1;
ListCell* l1;
ListCell* l2;
if (list_length(target_rangetblentry) <= 1) {
return;
}
foreach (l1, target_rangetblentry) {
RangeTblEntry* rte1 = (RangeTblEntry*)lfirst(l1);
rtindex2 = 0;
l2 = lnext(l1);
rtindex2 = rtindex1 + 1;
while (l2 != NULL) {
RangeTblEntry* rte2 = (RangeTblEntry*)lfirst(l2);
if (rte2->relid == rte1->relid) {
MergeTargetList(targetLists, rte1, rtindex1, rte2, rtindex2);
}
rtindex2++;
l2 = lnext(l2);
}
rtindex1++;
}
}
/*
* If a relation has no column updated in the resultRelations, it is redundant
* and remove it from the resultRelations.
*/
static List* remove_update_redundant_relation(List* resultRelations, List* target_rangetblentry)
{
if (list_length(resultRelations) == 1) {
return resultRelations;
}
RangeTblEntry* target_rte;
ListCell* rte = list_head(target_rangetblentry);
ListCell* res = list_head(resultRelations);
ListCell* res_pre = NULL;
ListCell* res_next = NULL;
while (res != NULL) {
res_next = lnext(res);
target_rte = (RangeTblEntry*)lfirst(rte);
if (bms_is_empty(target_rte->updatedCols)) {
resultRelations = list_delete_cell(resultRelations, res, res_pre);
} else {
res_pre = res;
}
res = res_next;
rte = lnext(rte);
}
return resultRelations;
}
static void CheckUpdateRelation(Relation targetrel)
{
// check column store relation distributed by replication
if (RelationIsColStore(targetrel) &&
targetrel->rd_locator_info &&
IsLocatorReplicated(targetrel->rd_locator_info->locatorType)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("replicated columnar table doesn't allow UPDATE")));
}
#ifdef ENABLE_MULTIPLE_NODES
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
#endif
if (RelationIsMatview(targetrel)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unsupported feature"),
errdetail("Materialized view doesn't allow UPDATE")));
}
#ifdef ENABLE_MULTIPLE_NODES
}
#endif
// check update permission
if (((unsigned int)RelationGetInternalMask(targetrel) & INTERNAL_MASK_DUPDATE)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("internal relation doesn't allow UPDATE")));
}
#ifdef ENABLE_MULTIPLE_NODES
// check if the target relation is being redistributed in read only mode
if (!u_sess->attr.attr_sql.enable_cluster_resize && targetrel != NULL &&
RelationInClusterResizingWriteErrorMode(targetrel)) {
ereport(ERROR,
(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
errmsg("%s is redistributing, please retry later.", targetrel->rd_rel->relname.data)));
}
#endif
}
void UpdateParseCheck(ParseState *pstate, Node *qry)
{
/*
* Top-level aggregates are simply disallowed in UPDATE, per spec. (From
* an implementation point of view, this is forced because the implicit
* ctid reference would otherwise be an ungrouped variable.)
*/
if (pstate->p_hasAggs) {
ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR), errmsg("cannot use aggregate function in UPDATE"),
parser_errposition(pstate, locate_agg_of_level(qry, 0))));
}
if (pstate->p_hasWindowFuncs) {
ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR), errmsg("cannot use window function in UPDATE"),
parser_errposition(pstate, locate_windowfunc(qry))));
}
}
/*
* transformUpdateStmt -
* transforms an update statement
*/
static Query* transformUpdateStmt(ParseState* pstate, UpdateStmt* stmt)
{
Query* qry = makeNode(Query);
Node* qual = NULL;
ParseNamespaceItem *nsitem = NULL;
qry->commandType = CMD_UPDATE;
pstate->p_is_insert = false;
ListCell* l;
pstate->p_has_ignore = stmt->hasIgnore;
/* set io state for backend status for the thread, we will use it to check user space */
pgstat_set_io_state(IOSTATE_READ);
/* process the WITH clause independently of all else */
if (stmt->withClause) {
#ifdef PGXC
SendCommandIdForInsertCte(qry, stmt->withClause);
#endif
qry->hasRecursive = stmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, stmt->withClause);
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
}
if (list_length(stmt->relationClause) > 1 || !IsA(linitial(stmt->relationClause), RangeVar)) {
/* add all relations from relationClause to resultRelations. */
transformFromClause(pstate, stmt->relationClause, true, false, true);
qry->resultRelations = pstate->p_updateRelations;
} else {
qry->resultRelations = setTargetTables(pstate, stmt->relationClause, true, true, ACL_UPDATE);
}
/* non-supported IGNORE cases */
Relation rel = ((Relation)linitial2(pstate->p_target_relation));
if (pstate->p_has_ignore && rel != NULL) {
if (RelationIsColumnFormat(rel)) {
ereport(ERROR, ((errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("IGNORE is not supported on UPDATE column orientated table."))));
}
}
/* subqueries in FROM cannot access the result relation */
int nsitem_count = list_length(pstate->p_varnamespace);
foreach(l, pstate->p_varnamespace) {
nsitem = (ParseNamespaceItem *)lfirst(l);
nsitem->p_lateral_only = true;
}
CheckUDRelations(pstate, stmt->sortClause, stmt->limitClause, stmt->returningList, false);
/*
* the FROM clause is non-standard SQL syntax. We used to be able to do
* this with REPLACE in POSTQUEL so we keep the feature.
*/
transformFromClause(pstate, stmt->fromClause);
qry->targetList = transformTargetList(pstate, stmt->targetList, EXPR_KIND_UPDATE_TARGET);
qual = transformWhereClause(pstate, stmt->whereClause, EXPR_KIND_WHERE, "WHERE");
/* remaining clauses can reference the result relation normally */
foreach (l, pstate->p_varnamespace) {
if (nsitem_count == 0) {
break;
}
nsitem = (ParseNamespaceItem *)lfirst(l);
nsitem->p_lateral_only = false;
nsitem_count--;
}
/*
* Now we are done with SELECT-like processing, and can get on with
* transforming the target list to match the UPDATE target columns.
*/
qry->targetList = transformUpdateTargetList(pstate, qry->targetList, stmt->targetList, qry->resultRelations);
transformLimitSortClause(pstate, stmt, qry, false);
qry->resultRelations = remove_update_redundant_relation(qry->resultRelations, pstate->p_target_rangetblentry);
qry->returningList = transformReturningList(pstate, stmt->returningList);
if (qry->returningList != NIL && RelationIsColStore(((Relation)linitial(pstate->p_target_relation)))) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("column stored relation doesn't support UPDATE returning")));
}
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
qry->hasSubLinks = pstate->p_hasSubLinks;
UpdateParseCheck(pstate, (Node *)qry);
assign_query_collations(pstate, qry);
qry->hintState = stmt->hintState;
qry->hasIgnore = stmt->hasIgnore;
return qry;
}
char* checkUpdateResTargetName(Relation rd, RangeVar* rel, ResTarget* res, bool* matchRelname)
{
char* resname = NULL;
AttrNumber attrnoIndName = InvalidAttrNumber;
AttrNumber attrnoResName = InvalidAttrNumber;
Oid attrtypeid = InvalidOid;
Oid typrelid = InvalidOid;
/* the length of indireciton can't less than 1 and the first item type must not be A_Indices */
if ((list_length(res->indirection) < 1) || IsA(linitial(res->indirection), A_Indices)) {
return res->name;
}
/* name must relname or aliasname */
if ((strcmp(rel->relname, res->name) != 0) &&
((rel->alias == NULL) || (strcmp((rel->alias)->aliasname, res->name) != 0))) {
return res->name;
}
/*
* if meet set_clause like a.b.c = 1 then the indirection will exceed one,
* so treat first indirection as tablename/alisename like a in this ex.
*/
resname = pstrdup(strVal(linitial(res->indirection)));
if (list_length(res->indirection) > 1) {
*matchRelname = true;
return resname;
}
/*
* Here only meet set_clause which has two elements like a.b or test.b.
* So first keep forward-compatibility behavior of old version, that results
* if a is a composite type and also relname/alisename then we first handle it as
* composite type.
* Otherwise we handle it like a.b like relname/alisename, then a is a relname just
* update set_clause over.
*/
attrnoResName = attnameAttNum(rd, res->name, true);
attrnoIndName = attnameAttNum(rd, resname, true);
if (attrnoResName > 0) {
attrtypeid = attnumTypeId(rd, attrnoResName);
if (attrtypeid) {
typrelid = typeidTypeRelid(attrtypeid);
if (typrelid && ((get_attnum(typrelid, resname) != InvalidAttrNumber) || (attrnoIndName <= 0))) {
ereport(
NOTICE, (errmsg("update field '%s' of column '%s', though it's ambiguous.", resname, res->name)));
return res->name;
}
}
}
*matchRelname = true;
return resname;
}
/* Find the attrno corresponding to ResTarget from target tables. */
static int fixUpdateResTargetName(ParseState* pstate, List* resultRelations, ResTarget* res, int* rti,
Relation* rd, RangeTblEntry** rte)
{
ListCell* l1;
ListCell* l2;
ListCell* l3;
ListCell* l4;
bool removeRelname = false, matchRelname = false;
char* resname = NULL;
char* resultResName = NULL;
int attrno, resultAttrno = InvalidAttrNumber;
bool isMatched = false;
forfour (l1, pstate->p_target_rangetblentry, l2, pstate->p_target_relation, l3, pstate->p_updateRangeVars,
l4, resultRelations) {
RangeTblEntry* target_rte = (RangeTblEntry*)lfirst(l1);
Relation targetrel = (Relation)lfirst(l2);
RangeVar* rangeVar = (RangeVar*)lfirst(l3);
int rtindex = lfirst_int(l4);
resname = checkUpdateResTargetName(targetrel, rangeVar, res, &matchRelname);
attrno = attnameAttNum(targetrel, resname, true);
if (attrno != InvalidAttrNumber) {
/*
* If the targetlist matches more than once and when target contains a relname(matchRelname)
* is the same as before(removeRelname), report error.
*/
if (isMatched == true && (matchRelname == removeRelname)) {
ereport(ERROR,
(errcode(ERRCODE_AMBIGUOUS_COLUMN),
errmsg("column reference \"%s\" is ambiguous", res->name),
parser_errposition(pstate, res->location)));
}
resultAttrno = attrno;
resultResName = resname;
*rti = rtindex;
*rd = targetrel;
*rte = target_rte;
isMatched = true;
/* Mark the target column as requiring update permissions */
target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
attrno - FirstLowInvalidHeapAttributeNumber);
}
if (matchRelname == true) {
removeRelname = true;
}
}
if (!isMatched) {
res->name = resname;
if (matchRelname) {
res->indirection = RemoveListCell(res->indirection, 1);
}
return InvalidAttrNumber;
}
if (removeRelname) {
res->name = resultResName;
res->indirection = RemoveListCell(res->indirection, 1);
}
return resultAttrno;
}
static void UndefinedColumnError(ParseState* pstate, ResTarget* origTarget, int targetRelationNum)
{
if (!origTarget->indirection) {
if (targetRelationNum == 1) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
origTarget->name,
RelationGetRelationName((Relation)linitial(pstate->p_target_relation))),
parser_errposition(pstate, origTarget->location)));
}
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" does not exist",
origTarget->name),
parser_errposition(pstate, origTarget->location)));
} else {
char* resname = pstrdup((char*)(((Value*)lfirst(list_head(origTarget->indirection)))->val.str));
if (targetRelationNum == 1) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s.%s\" of relation \"%s\" does not exist",
origTarget->name,
resname,
RelationGetRelationName((Relation)linitial(pstate->p_target_relation))),
parser_errposition(pstate, origTarget->location)));
}
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s.%s\" does not exist",
origTarget->name,
resname),
parser_errposition(pstate, origTarget->location)));
}
}
/*
* checkSRFInMultiUpdate
* Verify that a set-returning function is called in multiple-update and throw a error.
*/
static inline void checkSRFInMultiUpdate(Expr* expr, int targetRelationNum)
{
if (targetRelationNum == 1)
return;
if ((expr->type == T_FuncExpr) && ((FuncExpr*)expr)->funcretset == true) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("set-returning functions are not allowed in multiple UPDATE")));
}
}
/*
* transformUpdateTargetList -
* handle SET clause in UPDATE/INSERT ... DUPLICATE KEY UPDATE
*/
static List* transformUpdateTargetList(ParseState* pstate, List* qryTlist, List* origTlist, List* resultRelations)
{
List* tlist = NIL;
RangeTblEntry* target_rte = NULL;
ListCell* tl;
ListCell* orig_tl;
Relation targetrel = NULL;
int rtindex = 0;
int targetRelationNum = list_length(pstate->p_target_relation);
int rangeTableNum = list_length(pstate->p_rtable);
List** new_tle = (List**)palloc0(rangeTableNum * sizeof(List*));
/* Prepare to assign non-conflicting resnos to resjunk attributes */
pstate->p_next_resno = 1;
foreach_cell (cell, pstate->p_target_relation) {
targetrel = (Relation)lfirst(cell);
pstate->p_next_resno += targetrel->rd_rel->relnatts;
}
/* Prepare non-junk columns for assignment to target table */
forboth (tl, qryTlist, orig_tl, origTlist) {
TargetEntry* tle = (TargetEntry*)lfirst(tl);
ResTarget* origTarget = (ResTarget*)lfirst(orig_tl);
int attrno;
if (tle->resjunk) {
/*
* Resjunk nodes need no additional processing, but be sure they
* have resnos that do not match any target columns; else rewriter
* or planner might get confused. They don't need a resname
* either.
*/
tle->resno = (AttrNumber)pstate->p_next_resno++;
tle->resname = NULL;
continue;
}
attrno = fixUpdateResTargetName(pstate, resultRelations, origTarget, &rtindex, &targetrel, &target_rte);
if (attrno == InvalidAttrNumber) {
UndefinedColumnError(pstate, origTarget, targetRelationNum);
}
updateTargetListEntry(pstate, tle, origTarget->name, attrno, origTarget->indirection, origTarget->location,
targetrel, target_rte);
checkSRFInMultiUpdate(tle->expr, targetRelationNum);
tle->rtindex = rtindex;
new_tle[rtindex - 1] = lappend(new_tle[rtindex - 1], tle);
}
/*
* If there are actually the same result relations by different alias
* or synonym in multiple update, merge their targetLists.
*/
transformMultiTargetList(pstate->p_target_rangetblentry, new_tle);
if (targetRelationNum == 1) {
int i = linitial_int(resultRelations);
tlist = new_tle[i - 1];
} else {
for (int i = 0; i < rangeTableNum; i++) {
if (new_tle[i]) {
tlist = list_concat(tlist, new_tle[i]);
}
}
}
pfree(new_tle);
return tlist;
}
/*
* transformReturningList -
* handle a RETURNING clause in INSERT/UPDATE/DELETE
*/
static List* transformReturningList(ParseState* pstate, List* returningList)
{
List* rlist = NIL;
int save_next_resno;
bool save_hasAggs = false;
bool save_hasWindowFuncs = false;
if (returningList == NIL) {
return NIL; /* nothing to do */
}
/*
* We need to assign resnos starting at one in the RETURNING list. Save
* and restore the main tlist's value of p_next_resno, just in case
* someone looks at it later (probably won't happen).
*/
save_next_resno = pstate->p_next_resno;
pstate->p_next_resno = 1;
/* save other state so that we can detect disallowed stuff */
save_hasAggs = pstate->p_hasAggs;
pstate->p_hasAggs = false;
save_hasWindowFuncs = pstate->p_hasWindowFuncs;
pstate->p_hasWindowFuncs = false;
/* transform RETURNING identically to a SELECT targetlist */
rlist = transformTargetList(pstate, returningList, EXPR_KIND_RETURNING);
/* check for disallowed stuff */
/* aggregates not allowed (but subselects are okay) */
if (pstate->p_hasAggs) {
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in RETURNING"),
parser_errposition(pstate, locate_agg_of_level((Node*)rlist, 0))));
}
if (pstate->p_hasWindowFuncs) {
ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR),
errmsg("cannot use window function in RETURNING"),
parser_errposition(pstate, locate_windowfunc((Node*)rlist))));
}
/* mark column origins */
markTargetListOrigins(pstate, rlist);
/* resolve any still-unresolved output columns as being type text */
if (pstate->p_resolve_unknowns) {
resolveTargetListUnknowns(pstate, rlist);
}
/* restore state */
pstate->p_next_resno = save_next_resno;
pstate->p_hasAggs = save_hasAggs;
pstate->p_hasWindowFuncs = save_hasWindowFuncs;
return rlist;
}
/*
* transformDeclareCursorStmt -
* transform a DECLARE CURSOR Statement
*
* DECLARE CURSOR is a hybrid case: it's an optimizable statement (in fact not
* significantly different from a SELECT) as far as parsing/rewriting/planning
* are concerned, but it's not passed to the executor and so in that sense is
* a utility statement. We transform it into a Query exactly as if it were
* a SELECT, then stick the original DeclareCursorStmt into the utilityStmt
* field to carry the cursor name and options.
*/
static Query* transformDeclareCursorStmt(ParseState* pstate, DeclareCursorStmt* stmt)
{
Query* result = NULL;
/*
* Don't allow both SCROLL and NO SCROLL to be specified
*/
if ((stmt->options & CURSOR_OPT_SCROLL) && (stmt->options & CURSOR_OPT_NO_SCROLL)) {
ereport(
ERROR, (errcode(ERRCODE_INVALID_CURSOR_DEFINITION), errmsg("cannot specify both SCROLL and NO SCROLL")));
}
result = transformStmt(pstate, stmt->query);
/* Grammar should not have allowed anything but SELECT */
if (!IsA(result, Query) || result->commandType != CMD_SELECT || result->utilityStmt != NULL) {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("unexpected non-SELECT command in DECLARE CURSOR")));
}
/*
* We also disallow data-modifying WITH in a cursor. (This could be
* allowed, but the semantics of when the updates occur might be
* surprising.)
*/
if (result->hasModifyingCTE) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("DECLARE CURSOR must not contain data-modifying statements in WITH")));
}
/* FOR UPDATE and WITH HOLD are not compatible */
if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_HOLD)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("DECLARE CURSOR WITH HOLD ... FOR UPDATE/SHARE is not supported"),
errdetail("Holdable cursors must be READ ONLY.")));
}
/* FOR UPDATE and SCROLL are not compatible */
if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_SCROLL)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("DECLARE SCROLL CURSOR ... FOR UPDATE/SHARE is not supported"),
errdetail("Scrollable cursors must be READ ONLY.")));
}
/* FOR UPDATE and INSENSITIVE are not compatible */
if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_INSENSITIVE)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("DECLARE INSENSITIVE CURSOR ... FOR UPDATE/SHARE is not supported"),
errdetail("Insensitive cursors must be READ ONLY.")));
}
/* We won't need the raw querytree any more */
stmt->query = NULL;
result->utilityStmt = (Node*)stmt;
return result;
}
/*
* transformExplainStmt -
* transform an EXPLAIN Statement
*
* EXPLAIN is like other utility statements in that we emit it as a
* CMD_UTILITY Query node; however, we must first transform the contained
* query. We used to postpone that until execution, but it's really necessary
* to do it during the normal parse analysis phase to ensure that side effects
* of parser hooks happen at the expected time.
*/
static Query* transformExplainStmt(ParseState* pstate, ExplainStmt* stmt)
{
Query* result = NULL;
/* transform contained query, allowing SELECT INTO */
stmt->query = (Node*)transformTopLevelStmt(pstate, stmt->query);
/* represent the command as a utility Query */
result = makeNode(Query);
result->commandType = CMD_UTILITY;
result->utilityStmt = (Node*)stmt;
return result;
}
/*
* transformCreateTableAsStmt -
* transform a CREATE TABLE AS, SELECT ... INTO, or CREATE MATERIALIZED VIEW Statement
*
* As with EXPLAIN, transform the contained statement now.
*/
static Query* transformCreateTableAsStmt(ParseState* pstate, CreateTableAsStmt* stmt)
{
Query* result = NULL;
ListCell* lc = NULL;
/*
* Set relkind in IntoClause based on statement relkind. These are
* different types, because the parser users the ObjectType enumeration
* and the executor uses RELKIND_* defines.
*/
switch (stmt->relkind)
{
case (OBJECT_TABLE):
stmt->into->relkind = RELKIND_RELATION;
break;
case (OBJECT_MATVIEW):
stmt->into->relkind = RELKIND_MATVIEW;
break;
default:
elog(ERROR, "unrecognized object relkind: %d",
(int) stmt->relkind);
}
/* transform contained query */
stmt->query = (Node*)transformStmt(pstate, stmt->query);
if (u_sess->attr.attr_sql.sql_compatibility == B_FORMAT) {
/* CREATE TABLE AS SELECT is not allowed with Foreign Key and Tablelike Clause*/
foreach (lc, stmt->into->tableElts) {
Node* node = (Node*)lfirst(lc);
if (IsA(node, ColumnDef)) {
ColumnDef* col = (ColumnDef*) node;
ListCell* cell = NULL;
foreach(cell, col->constraints){
if (IsA(lfirst(cell), Constraint)) {
Constraint* constraint = (Constraint*) lfirst(cell);
if (constraint->contype == CONSTR_FOREIGN) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("CREATE TABLE AS SELECT is not allowed with Foreign Key")));
}
}
}
} else if (IsA(node, Constraint)) {
Constraint* constraint = (Constraint*) node;
if (constraint->contype == CONSTR_FOREIGN) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("CREATE TABLE AS SELECT is not allowed with Foreign Key")));
}
} else if (IsA(node, TableLikeClause)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("CREATE TABLE AS SELECT is not allowed with Tablelike Clause")));
}
}
}
/* if result type of new relation is unknown-type, then resolve as type TEXT */
foreach (lc, ((Query*)stmt->query)->targetList) {
TargetEntry* tle = (TargetEntry*)lfirst(lc);
if (exprType((Node*)tle->expr) == UNKNOWNOID) {
tle->expr = (Expr*)coerce_type(
pstate, (Node*)tle->expr, UNKNOWNOID, TEXTOID, -1, COERCION_IMPLICIT, COERCE_IMPLICIT_CAST, -1);
}
}
/* set io state for backend status for the thread, we will use it to check user space */
pgstat_set_io_state(IOSTATE_WRITE);
/* represent the command as a utility Query */
result = makeNode(Query);
result->commandType = CMD_UTILITY;
result->hintState = (HintState*)copyObject(((Query*)stmt->query)->hintState);
result->utilityStmt = (Node*)stmt;
return result;
}
#ifdef PGXC
#ifdef ENABLE_DISTRIBUTE_TEST
/*
* Check if given GUC variable can go through EXECUTE DIRECT
*/
static bool checkExecDirectVariableSetStmt(const Node* node)
{
if (nodeTag(node) == T_VariableSetStmt) {
if (pg_strcasecmp(((VariableSetStmt*)node)->name, "distribute_test_param") == 0) {
return true;
}
}
return false;
}
#endif
static void get_index_and_type_from_nodename(const char* node_name, int* node_index, char* node_type)
{
Oid node_oid;
if (node_name == NULL || node_index == NULL || node_type == NULL) {
return;
}
node_oid = get_pgxc_nodeoid(node_name);
if (!OidIsValid(node_oid)) {
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("PGXC Node %s: object not defined", node_name)));
}
*node_type = get_pgxc_nodetype(node_oid);
*node_index = PGXCNodeGetNodeId(node_oid, get_pgxc_nodetype(node_oid));
return;
}
RemoteQueryExecType fill_exec_type(char node_type, ExecDirectOption option)
{
RemoteQueryExecType exec_type;
switch (option) {
case EXEC_DIRECT_ON_LIST:
if (node_type == PGXC_NODE_COORDINATOR) {
exec_type = EXEC_ON_COORDS;
} else {
exec_type = EXEC_ON_DATANODES;
}
break;
case EXEC_DIRECT_ON_ALL_CN:
exec_type = EXEC_ON_COORDS;
break;
case EXEC_DIRECT_ON_ALL_DN:
exec_type = EXEC_ON_DATANODES;
break;
case EXEC_DIRECT_ON_ALL_NODES:
exec_type = EXEC_ON_ALL_NODES;
break;
default:
exec_type = EXEC_ON_NONE;
break;
}
return exec_type;
}
void check_node_index(int node_index, char* node_flag, int length_node_flag)
{
if ((node_flag != NULL) &&
(node_index < length_node_flag) &&
(node_index >= 0) &&
node_flag[node_index] == 0) {
node_flag[node_index] = 1;
} else {
ereport(ERROR,
(errcode(ERRCODE_OPERATE_NOT_SUPPORTED),
errmsg("node name in node list is not correct")));
}
}
static void fill_node_list_and_exec_type(const List* nodenames_list, ExecDirectOption option, RemoteQuery* query, bool* include_local)
{
char* node_name = NULL;
int node_index;
char node_type = PGXC_NODE_NONE;
ListCell* nodename_item = NULL;
List* index_list = NULL;
int number_of_cn = 0;
int number_of_dn = 0;
char* cn_flag = NULL;
char* dn_flag = NULL;
*include_local = false;
if (option == EXEC_DIRECT_ON_LIST) {
if (u_sess->pgxc_cxt.NumCoords > 0) {
cn_flag = (char*)palloc0(u_sess->pgxc_cxt.NumCoords);
}
if (u_sess->pgxc_cxt.NumDataNodes > 0) {
dn_flag = (char*)palloc0(u_sess->pgxc_cxt.NumDataNodes);
}
foreach(nodename_item, nodenames_list) {
node_name = strVal(lfirst(nodename_item));
get_index_and_type_from_nodename(node_name, &node_index, &node_type);
if (node_type == PGXC_NODE_COORDINATOR) {
check_node_index(node_index, cn_flag, u_sess->pgxc_cxt.NumCoords);
number_of_cn++;
if (node_index == u_sess->pgxc_cxt.PGXCNodeId - 1) {
*include_local = true;
continue;
}
} else if (node_type == PGXC_NODE_DATANODE) {
check_node_index(node_index, dn_flag, u_sess->pgxc_cxt.NumDataNodes);
number_of_dn++;
} else {
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NODE_STATE),
errmsg("unsupport node type: %d", node_type)));
}
index_list = lappend_int(index_list, node_index);
}
if ((number_of_cn != 0) && (number_of_dn != 0)) {
ereport(ERROR,
(errcode(ERRCODE_OPERATE_NOT_SUPPORTED),
errmsg("not support both coordinator and datanode in the execute list")));
}
}
query->exec_type = fill_exec_type(node_type, option);
query->exec_nodes->nodeList = index_list;
if ((option == EXEC_DIRECT_ON_ALL_CN) || (option == EXEC_DIRECT_ON_ALL_NODES)) {
*include_local = true;
}
pfree_ext(cn_flag);
pfree_ext(dn_flag);
return;
}
ExecDirectType set_exec_direct_type(bool is_local, CmdType command_type)
{
ExecDirectType type = EXEC_DIRECT_NONE;
if (is_local) {
if (command_type == CMD_UTILITY)
type = EXEC_DIRECT_LOCAL_UTILITY;
else
type = EXEC_DIRECT_LOCAL;
} else {
switch (command_type) {
case CMD_UTILITY:
type = EXEC_DIRECT_UTILITY;
break;
case CMD_SELECT:
type = EXEC_DIRECT_SELECT;
break;
case CMD_INSERT:
type = EXEC_DIRECT_INSERT;
break;
case CMD_UPDATE:
type = EXEC_DIRECT_UPDATE;
break;
case CMD_DELETE:
type = EXEC_DIRECT_DELETE;
break;
case CMD_UNKNOWN:
break;
default:
/* CMD_MERGE and CMD_NOTHING are handled before set up EXECUTE DIRECT flag */
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NODE_STATE),
errmsg("unrecognized commandType: %d", command_type)));
break;
}
}
return type;
}
void check_command_type(CmdType command_type)
{
if (command_type == CMD_MERGE) {
ereport(
ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("EXECUTE DIRECT cannot execute MERGE INTO query")));
}
if (command_type == CMD_NOTHING) {
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("EXECUTE DIRECT cannot execute CREATE RULE")));
}
}
/*
* Features not yet supported
* DML can be launched without errors but this could compromise data
* consistency, so block it.
*/
static void check_execute_direct_type_and_statement(ExecDirectType exec_direct_type, const Node* utility_statement, bool include_local)
{
if (!u_sess->attr.attr_common.xc_maintenance_mode && !u_sess->attr.attr_common.IsInplaceUpgrade &&
(exec_direct_type == EXEC_DIRECT_DELETE || exec_direct_type == EXEC_DIRECT_UPDATE ||
exec_direct_type == EXEC_DIRECT_INSERT)) {
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("EXECUTE DIRECT cannot execute DML queries")));
} else if (exec_direct_type == EXEC_DIRECT_UTILITY) {
if (include_local || (!IsExecDirectUtilityStmt(utility_statement) &&
#ifdef ENABLE_DISTRIBUTE_TEST
// just enable the VariableSetStmt named distribute_test_param for distribute test.
!checkExecDirectVariableSetStmt(utility_statement) &&
#endif
!u_sess->attr.attr_common.xc_maintenance_mode)) {
/* In case this statement is an utility, check if it is authorized */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("EXECUTE DIRECT cannot execute this utility query")));
}
} else if (exec_direct_type == EXEC_DIRECT_LOCAL_UTILITY &&
#ifdef ENABLE_DISTRIBUTE_TEST
// just enable the VariableSetStmt named distribute_test_param for distribute test.
!checkExecDirectVariableSetStmt(utility_statement) &&
#endif
!u_sess->attr.attr_common.xc_maintenance_mode) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("EXECUTE DIRECT cannot execute locally this utility query")));
}
return;
}
static Query* generate_query_execdirect_statement(const ExecDirectStmt* stmt)
{
Query* result = makeNode(Query);
char* query = NULL;
StringInfoData strinfo;
List* raw_parsetree_list = NIL;
ListCell* raw_parsetree_item = NULL;
// When the EXECUTE DIRECT ON 'xxxxx' statement is executed,
// once the 'xxxxx' statement is in error, the error will dislocation,
// So the space of several characters should be added to ensure the correctness of the error location.
// eg. EXECUTE DIRECT ON 'select a;', then 'select a;' will be replaced by ' select a;'
initStringInfo(&strinfo);
appendStringInfoSpaces(&strinfo, stmt->location + 1);
appendStringInfoString(&strinfo, stmt->query);
query = strinfo.data;
/* Transform the query into a raw parse list */
raw_parsetree_list = pg_parse_query(query);
/* EXECUTE DIRECT can just be executed with a single query */
if (list_length(raw_parsetree_list) > 1) {
ereport(
ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("EXECUTE DIRECT cannot execute multiple queries")));
}
/*
* Analyze the Raw parse tree
* EXECUTE DIRECT is restricted to one-step usage
*/
foreach (raw_parsetree_item, raw_parsetree_list) {
Node* parsetree = (Node*)lfirst(raw_parsetree_item);
/* Not allow EXECUTE DIRECT ON recursively */
if (IsA(parsetree, ExecDirectStmt)) {
ereport(
ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("EXECUTE DIRECT cannot execute recursively")));
}
result = parse_analyze(parsetree, query, NULL, 0);
}
pfree_ext(strinfo.data);
return result;
}
static void init_execdirect_utility_stmt(RemoteQuery* step, const char* statement)
{
step->cursor = NULL;
step->base_tlist = NIL;
step->force_autocommit = false;
step->read_only = true;
step->sql_statement = pstrdup(statement);
step->combine_type = COMBINE_TYPE_SAME;
}
/*
* find agg functions which need add finalize function on sql statement in deparse_query
*/
static bool check_agg_in_execute_direct_query(Node* node, void* context)
{
if (node == NULL || IS_PGXC_DATANODE) {
return false;
}
if (IsA(node, Aggref)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("EXECUTE DIRECT on multinode not support agg functions.")));
}
if (IsA(node, Query)) {
if (query_tree_walker((Query*)node,
(bool (*)())check_agg_in_execute_direct_query, (void*)node, 0)) {
return true;
}
}
return expression_tree_walker(node, (bool (*)())check_agg_in_execute_direct_query, context);
}
/*
* transformExecDirectStmt -
* transform an EXECUTE DIRECT Statement
*
* Handling is depends if we should execute on nodes or on Coordinator.
* To execute on nodes we return CMD_UTILITY query having one T_RemoteQuery node
* with the inner statement as a sql_command.
* If statement is to run on Coordinator we should parse inner statement and
* analyze resulting query tree.
*/
static Query* transformExecDirectStmt(ParseState* pstate, ExecDirectStmt* stmt)
{
Query* result = NULL;
List* nodelist = stmt->node_names;
ExecDirectOption option = stmt->exec_option;
RemoteQuery* step = makeNode(RemoteQuery);
bool is_local = false;
bool include_local = false;
char nodetype = PGXC_NODE_COORDINATOR;
int node_index;
/* Support not available on Datanodes except single node */
if (IS_PGXC_DATANODE && !IS_SINGLE_NODE)
ereport(
ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("EXECUTE DIRECT cannot be executed on a Datanode")));
Assert(IS_PGXC_COORDINATOR || IS_SINGLE_NODE);
step->exec_nodes = makeNode(ExecNodes);
result = generate_query_execdirect_statement(stmt);
if (list_length(nodelist) == 1) {
get_index_and_type_from_nodename(strVal(linitial(nodelist)), &node_index, &nodetype);
/* Check if node is requested is the self-node or not */
if ((nodetype == PGXC_NODE_COORDINATOR && node_index == u_sess->pgxc_cxt.PGXCNodeId - 1) || IS_SINGLE_NODE)
is_local = true;
step->exec_type = fill_exec_type(nodetype, option);
step->exec_nodes->nodeList = lappend_int(step->exec_nodes->nodeList, node_index);
} else {
check_agg_in_execute_direct_query((Node*)result, (void*)result);
fill_node_list_and_exec_type(nodelist, option, step, &include_local);
step->is_remote_function_query = true;
}
/* Needed by planner */
result->sql_statement = pstrdup(stmt->query);
init_execdirect_utility_stmt(step, result->sql_statement);
check_command_type(result->commandType);
step->exec_direct_type = set_exec_direct_type(is_local, result->commandType);
check_execute_direct_type_and_statement(step->exec_direct_type, result->utilityStmt, include_local);
/* Associate newly-created RemoteQuery node to the returned Query result */
is_local = (is_local || include_local);
result->is_local = is_local;
if (!is_local || include_local) {
result->utilityStmt = (Node*)step;
}
/* Not allow SELECT with normal table on CN (no data) */
if ((step->exec_type == EXEC_ON_COORDS || (step->exec_type == EXEC_ON_ALL_NODES)) &&
result->commandType == CMD_SELECT &&
containing_ordinary_table((Node*)result)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("EXECUTE DIRECT cannot execute SELECT query with normal table on coordinator")));
}
return result;
}
/*
* Check if given node is authorized to go through EXECUTE DIRECT
*/
static bool IsExecDirectUtilityStmt(const Node* node)
{
bool res = true;
if (node == NULL) {
return res;
}
switch (nodeTag(node)) {
/*
* CREATE/DROP TABLESPACE are authorized to control
* tablespace at single node level.
*/
case T_CreateTableSpaceStmt:
case T_DropTableSpaceStmt:
res = true;
break;
default:
res = false;
break;
}
return res;
}
/*
* Returns whether or not the rtable (and its subqueries)
* contain any relation who is the parent of
* the passed relation
*/
static bool is_relation_child(RangeTblEntry* child_rte, List* rtable)
{
ListCell* item = NULL;
if (child_rte == NULL || rtable == NULL) {
return false;
}
if (child_rte->rtekind != RTE_RELATION) {
return false;
}
foreach (item, rtable) {
RangeTblEntry* rte = (RangeTblEntry*)lfirst(item);
if (rte->rtekind == RTE_RELATION) {
if (is_rel_child_of_rel(child_rte, rte)) {
return true;
}
} else if (rte->rtekind == RTE_SUBQUERY) {
return is_relation_child(child_rte, rte->subquery->rtable);
}
}
return false;
}
/*
* Returns whether the passed RTEs have a parent child relationship
*/
static bool is_rel_child_of_rel(RangeTblEntry* child_rte, RangeTblEntry* parent_rte)
{
Oid parentOID;
bool res = false;
Relation relation;
SysScanDesc scan;
ScanKeyData key[1];
HeapTuple inheritsTuple;
Oid inhrelid;
/* Does parent RT entry allow inheritance? */
if (!parent_rte->inh) {
return false;
}
/* Ignore any already-expanded UNION ALL nodes */
if (parent_rte->rtekind != RTE_RELATION) {
return false;
}
/* Fast path for common case of childless table */
parentOID = parent_rte->relid;
if (!has_subclass(parentOID)) {
return false;
}
/* Assume we did not find any match */
res = false;
/* Scan pg_inherits and get all the subclass OIDs one by one. */
relation = heap_open(InheritsRelationId, AccessShareLock);
ScanKeyInit(&key[0], Anum_pg_inherits_inhparent, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(parentOID));
scan = systable_beginscan(relation, InheritsParentIndexId, true, NULL, 1, key);
while ((inheritsTuple = systable_getnext(scan)) != NULL) {
inhrelid = ((Form_pg_inherits)GETSTRUCT(inheritsTuple))->inhrelid;
/* Did we find the Oid of the passed RTE in one of the children? */
if (child_rte->relid == inhrelid) {
res = true;
break;
}
}
systable_endscan(scan);
heap_close(relation, AccessShareLock);
return res;
}
#endif
/*
* Check for features that are not supported together with FOR [KEY] UPDATE/SHARE.
*
* exported so planner can check again after rewriting, query pullup, etc
*/
void CheckSelectLocking(Query* qry)
{
if (qry->setOperations) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE%s is not allowed with UNION/INTERSECT/EXCEPT",
NOKEYUPDATE_KEYSHARE_ERRMSG)));
}
if (qry->distinctClause != NIL) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE%s is not allowed with DISTINCT clause", NOKEYUPDATE_KEYSHARE_ERRMSG)));
}
if (qry->groupClause != NIL) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE%s is not allowed with GROUP BY clause", NOKEYUPDATE_KEYSHARE_ERRMSG)));
}
if (qry->havingQual != NULL) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE%s is not allowed with HAVING clause", NOKEYUPDATE_KEYSHARE_ERRMSG)));
}
if (qry->hasAggs) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE%s is not allowed with aggregate functions",
NOKEYUPDATE_KEYSHARE_ERRMSG)));
}
if (qry->hasWindowFuncs) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE%s is not allowed with window functions", NOKEYUPDATE_KEYSHARE_ERRMSG)));
}
if (expression_returns_set((Node*)qry->targetList)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE%s is not allowed with set-returning functions in the target list",
NOKEYUPDATE_KEYSHARE_ERRMSG)));
}
}
static bool CheckViewBasedOnCstore(Relation targetrel)
{
Assert(RelationIsView(targetrel));
Query* viewquery = get_view_query(targetrel);
ListCell* l = NULL;
foreach (l, viewquery->jointree->fromlist) {
RangeTblRef* rtr = (RangeTblRef*)lfirst(l);
RangeTblEntry* base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
Relation base_rel = try_relation_open(base_rte->relid, AccessShareLock);
if (RelationIsColStore(base_rel) || (RelationIsView(base_rel) && CheckViewBasedOnCstore(base_rel))) {
heap_close(base_rel, AccessShareLock);
return true;
}
heap_close(base_rel, AccessShareLock);
}
return false;
}
/*
* Transform a FOR [KEY] UPDATE/SHARE clause
*
* This basically involves replacing names by integer relids.
*
* NB: if you need to change this, see also markQueryForLocking()
* in rewriteHandler.c, and isLockedRefname() in parse_relation.c.
*/
static void transformLockingClause(ParseState* pstate, Query* qry, LockingClause* lc, bool pushedDown)
{
List* lockedRels = lc->lockedRels;
ListCell* l = NULL;
ListCell* rt = NULL;
Index i;
LockingClause* allrels = NULL;
Relation rel;
CheckSelectLocking(qry);
/* make a clause we can pass down to subqueries to select all rels */
allrels = makeNode(LockingClause);
allrels->lockedRels = NIL; /* indicates all rels */
/* The strength of lc is not set at old version and distribution. Set it according to forUpdate. */
if (t_thrd.proc->workingVersionNum < ENHANCED_TUPLE_LOCK_VERSION_NUM
#ifdef ENABLE_MULTIPLE_NODES
|| true
#endif
) {
lc->strength = lc->forUpdate ? LCS_FORUPDATE : LCS_FORSHARE;
}
allrels->strength = lc->strength;
allrels->waitSec = lc->waitSec;
/* The processing delay of the ProcSleep function is in milliseconds. Set the delay to int_max/1000. */
/* The processing delay of the ProcSleep function is in milliseconds. Set the delay to int_max/1000. */
if (lc->waitSec > (MAX_INT32 / MILLISECONDS_PER_SECONDS)) {
ereport(ERROR,
(errmodule(MOD_OPT_PLANNER), errcode(ERRCODE_INVALID_OPTION),
errmsg("The delay ranges from 0 to 2147483."),
errdetail("N/A"),
errcause("Invalid input parameter."),
erraction("Modify SQL statement according to the manual.")));
}
allrels->waitPolicy = lc->waitPolicy;
if (lockedRels == NIL) {
/* all regular tables used in query */
i = 0;
foreach (rt, qry->rtable) {
RangeTblEntry* rte = (RangeTblEntry*)lfirst(rt);
++i;
switch (rte->rtekind) {
case RTE_RELATION:
rel = relation_open(rte->relid, AccessShareLock);
if (RelationIsColStore(rel)) {
heap_close(rel, AccessShareLock);
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with "
"column table \"%s\"", rte->eref->aliasname)));
} else if (RelationIsView(rel) && CheckViewBasedOnCstore(rel)) {
heap_close(rel, AccessShareLock);
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with "
"view \"%s\" based on column table", rte->eref->aliasname)));
} else {
if (!RelationIsAstoreFormat(rel) && lc->waitPolicy == LockWaitSkip) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE with skip locked "
"only be used with AStore table \"%s\"", rte->eref->aliasname)));
}
}
heap_close(rel, AccessShareLock);
applyLockingClause(qry, i, lc->strength, lc->waitPolicy, pushedDown, lc->waitSec);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
break;
case RTE_SUBQUERY:
applyLockingClause(qry, i, lc->strength, lc->waitPolicy, pushedDown, lc->waitSec);
/*
* FOR [KEY] UPDATE/SHARE of subquery is propagated to all of
* subquery's rels, too. We could do this later (based on
* the marking of the subquery RTE) but it is convenient
* to have local knowledge in each query level about which
* rels need to be opened with RowShareLock.
*/
transformLockingClause(pstate, rte->subquery, allrels, true);
break;
case RTE_CTE:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a WITH query")));
break;
default:
/* ignore JOIN, SPECIAL, FUNCTION, VALUES, CTE RTEs */
break;
}
}
} else {
/* just the named tables */
foreach (l, lockedRels) {
RangeVar* thisrel = (RangeVar*)lfirst(l);
/* For simplicity we insist on unqualified alias names here */
if (thisrel->catalogname || thisrel->schemaname) {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("SELECT FOR UPDATE/SHARE%s must specify unqualified "
"relation names", NOKEYUPDATE_KEYSHARE_ERRMSG),
parser_errposition(pstate, thisrel->location)));
}
i = 0;
foreach (rt, qry->rtable) {
RangeTblEntry* rte = (RangeTblEntry*)lfirst(rt);
++i;
if (strcmp(rte->eref->aliasname, thisrel->relname) == 0) {
switch (rte->rtekind) {
case RTE_RELATION:
rel = relation_open(rte->relid, AccessShareLock);
if (RelationIsColStore(rel)) {
heap_close(rel, AccessShareLock);
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE%s cannot be used with column table \"%s\"",
NOKEYUPDATE_KEYSHARE_ERRMSG, rte->eref->aliasname),
parser_errposition(pstate, thisrel->location)));
} else if (RelationIsView(rel) && CheckViewBasedOnCstore(rel)) {
heap_close(rel, AccessShareLock);
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE%s cannot be used with view \"%s\" based on"
" column table", NOKEYUPDATE_KEYSHARE_ERRMSG, rte->eref->aliasname),
parser_errposition(pstate, thisrel->location)));
} else {
if (!RelationIsAstoreFormat(rel) && lc->waitPolicy == LockWaitSkip) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE with skip locked "
"only be used with row table \"%s\"", rte->eref->aliasname),
parser_errposition(pstate, thisrel->location)));
}
}
heap_close(rel, AccessShareLock);
applyLockingClause(qry, i, lc->strength, lc->waitPolicy, pushedDown,
lc->waitSec);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
break;
case RTE_SUBQUERY:
applyLockingClause(qry, i, lc->strength, lc->waitPolicy, pushedDown,
lc->waitSec);
/* see comment above */
transformLockingClause(pstate, rte->subquery, allrels, true);
break;
case RTE_JOIN:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE%s cannot be applied to a join",
NOKEYUPDATE_KEYSHARE_ERRMSG),
parser_errposition(pstate, thisrel->location)));
break;
case RTE_FUNCTION:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE%s cannot be applied to a function",
NOKEYUPDATE_KEYSHARE_ERRMSG),
parser_errposition(pstate, thisrel->location)));
break;
case RTE_VALUES:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE%s cannot be applied to VALUES",
NOKEYUPDATE_KEYSHARE_ERRMSG),
parser_errposition(pstate, thisrel->location)));
break;
case RTE_CTE:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE%s cannot be applied to a WITH query",
NOKEYUPDATE_KEYSHARE_ERRMSG),
parser_errposition(pstate, thisrel->location)));
break;
default:
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized RTE type: %d", (int)rte->rtekind)));
break;
}
break; /* out of foreach loop */
}
}
if (rt == NULL) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" in FOR UPDATE/SHARE//NO KEY UPDATE/KEY SHARE clause not found "
"in FROM clause", thisrel->relname),
parser_errposition(pstate, thisrel->location)));
}
}
}
}
/*
* Record locking info for a single rangetable item
*/
void applyLockingClause(Query* qry, Index rtindex, LockClauseStrength strength, LockWaitPolicy waitPolicy, bool pushedDown,
int waitSec)
{
RowMarkClause* rc = NULL;
/* If it's an explicit clause, make sure hasForUpdate gets set */
if (!pushedDown) {
qry->hasForUpdate = true;
}
/* Check for pre-existing entry for same rtindex */
if ((rc = get_parse_rowmark(qry, rtindex)) != NULL) {
/*
* If the same RTE is specified for more than one locking strength,
* treat is as the strongest. (Reasonable, since you can't take both a
* shared and exclusive lock at the same time; it'll end up being
* exclusive anyway.)
*
* Similarly, if the same RTE is specified with more than one lock wait
* policy, consider that NOWAIT wins over SKIP LOCKED, which in turn
* wins over waiting for the lock (the default). This is a bit more
* debatable but raising an error doesn't seem helpful. (Consider for
* instance SELECT FOR UPDATE NOWAIT from a view that internally
* contains a plain FOR UPDATE spec.) Having NOWAIT win over SKIP
* LOCKED is reasonable since the former throws an error in case of
* coming across a locked tuple, which may be undesirable in some cases
* but it seems better than silently returning inconsistent results.
*
* And of course pushedDown becomes false if any clause is explicit.
*/
rc->strength = Max(rc->strength, strength);
rc->forUpdate = rc->strength == LCS_FORUPDATE;
rc->waitPolicy = Max(rc->waitPolicy, waitPolicy);
rc->waitSec = Max(rc->waitSec, waitSec);
rc->pushedDown = rc->pushedDown && pushedDown;
return;
}
/* Make a new RowMarkClause */
rc = makeNode(RowMarkClause);
rc->rti = rtindex;
rc->forUpdate = strength == LCS_FORUPDATE;
rc->strength = strength;
rc->waitPolicy = waitPolicy;
rc->waitSec = waitSec;
rc->pushedDown = pushedDown;
qry->rowMarks = lappend(qry->rowMarks, rc);
}
/*
* Check if there is foreign table that is included in one insert statement.
*
* input:
* rte_table_list: the list of p_rtable of ParseState.
*
* output:
* true: there is foreign table included in insert statement.
*/
bool checkForeignTableExist(List* rte_table_list)
{
bool ret_val = false;
ListCell* lc = NULL;
RangeTblEntry* rte = NULL;
if (rte_table_list == NULL) {
return ret_val;
}
foreach (lc, rte_table_list) {
rte = (RangeTblEntry*)lfirst(lc);
if (rte->relkind == RELKIND_FOREIGN_TABLE || rte->relkind == RELKIND_STREAM) {
ret_val = true;
break;
}
}
return ret_val;
}
/*
* Function name: set_subquery_is_under_insert
* set p_is_in_insert for paserstate, to mark this is sub query under one insert.
*
* input:
* subParseState: ParseState of sub query.
*
*
* output:
* void
*/
void set_subquery_is_under_insert(ParseState* subParseState)
{
ParseState* ancestor_ps = NULL;
ParseState* temp_ps = NULL;
if (unlikely(subParseState == NULL)) {
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("subParseState should not be null")));
}
/* If the subParseState's parentParseState is NULL then do nothing. */
if (subParseState->parentParseState == NULL) {
return;
}
/* Find top level parsestate. */
temp_ps = subParseState;
while (temp_ps != NULL) {
if (temp_ps->parentParseState != NULL) {
ancestor_ps = temp_ps->parentParseState;
/* If this is a insert statement. then mark sub parsestate is under insert. */
if (ancestor_ps->p_is_insert) {
subParseState->p_is_in_insert = ancestor_ps->p_is_insert;
break;
}
}
temp_ps = temp_ps->parentParseState;
}
}
/*
* Function name: set_ancestor_ps_contain_foreigntbl
* When sub query find a foreign table. call this function to set top level parse
* state that is insert type to set p_is_foreignTbl_exist to true;
* Input:
* subParseState: the ParseState of sub query.
* Output:
* void
*/
void set_ancestor_ps_contain_foreigntbl(ParseState* subParseState)
{
ParseState* ancestor_ps = NULL;
ParseState* temp_ps = NULL;
if (unlikely(subParseState == NULL)) {
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("subParseState should not be null")));
}
/* If the subParseState's parentParseState is NULL then do nothing. */
if (subParseState->parentParseState == NULL) {
return;
}
/* We go back through parentParseState to find top level parseState. */
temp_ps = subParseState;
while (temp_ps != NULL) {
if (temp_ps->parentParseState != NULL) {
ancestor_ps = temp_ps->parentParseState;
/*
* Because there should at least one insert statement on top of
* this select statement, we mark find foreign table in all top level
* insert statements.
*/
if (ancestor_ps->p_is_insert) {
ancestor_ps->p_is_foreignTbl_exist = true;
}
}
temp_ps = temp_ps->parentParseState;
}
}
/*
* @Description: This group clause if include groupingSet.
* @in groupClause - group clause.
* @return - If include return true else return false.
*/
static bool include_groupingset(Node* groupClause)
{
if (groupClause == NULL) {
return false;
}
if (IsA(groupClause, List)) {
List* group_list = (List*)groupClause;
ListCell* lc = NULL;
foreach (lc, group_list) {
Node* node = (Node*)lfirst(lc);
if (include_groupingset(node)) {
return true;
}
}
} else if (IsA(groupClause, GroupingSet)) {
return true;
}
return false;
}
/*
* @Description: Transform const expr in groupClause to columnref struct.
* @in groupClause - group clause.
* @in targetList - query targetlist.
*/
static void transformGroupConstToColumn(ParseState* pstate, Node* groupClause, List* targetList)
{
if (groupClause == NULL) {
return;
}
if (IsA(groupClause, List)) {
List* group_list = (List*)groupClause;
ListCell* lc = NULL;
foreach (lc, group_list) {
Node* node = (Node*)lfirst(lc);
/* Replace this const by expr in targetlist. */
if (IsA(node, A_Const)) {
Value* val = &((A_Const*)node)->val;
int location = ((A_Const*)node)->location;
if (!IsA(val, Integer)) {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("non-integer constant in group clause"),
parser_errposition(pstate, location)));
}
long target_pos = intVal(val);
if (target_pos > 0 && target_pos <= list_length(targetList)) {
TargetEntry* tle = (TargetEntry*)list_nth(targetList, target_pos - 1);
lfirst(lc) = copyObject(tle->expr);
pfree_ext(node);
} else {
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("%s position %ld is not in select list", "GROUP BY", target_pos),
parser_errposition(pstate, location)));
}
} else {
transformGroupConstToColumn(pstate, node, targetList);
}
}
} else if (IsA(groupClause, GroupingSet)) {
GroupingSet* grouping_set = (GroupingSet*)groupClause;
transformGroupConstToColumn(pstate, (Node*)grouping_set->content, targetList);
}
}
static bool checkAllowedTableCombination(ParseState* pstate)
{
RangeTblEntry* rte = NULL;
ListCell* lc = NULL;
bool has_ustore = false;
bool has_else = false;
// Check range table list for the presence of
// relations with different storages
foreach(lc, pstate->p_rtable) {
rte = (RangeTblEntry*)lfirst(lc);
if (rte && rte->rtekind == RTE_RELATION) {
if (rte->is_ustore) {
has_ustore = true;
} else {
has_else = true;
}
}
}
// Check target table for the type of storage
foreach(lc, pstate->p_target_rangetblentry) {
rte = (RangeTblEntry*)lfirst(lc);
if (rte && rte->rtekind == RTE_RELATION) {
if (rte->is_ustore) {
has_ustore = true;
} else {
has_else = true;
}
}
}
Assert(has_ustore || has_else);
return !(has_ustore && has_else);
}