mirror of
https://github.com/postgres/postgres.git
synced 2025-05-31 03:21:24 +03:00
Since 3db72eb, the query ID of utilities is generated using the Query structure, making the use of the query string in JumbleQuery() unnecessary. This commit removes the argument "querytext" from JumbleQuery(). Reported-by: Joe Conway Reviewed-by: Nathan Bossart Discussion: https://postgr.es/m/ZJlQAWE4COFqHuAV@paquier.xyz
3545 lines
107 KiB
C
3545 lines
107 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* analyze.c
|
|
* 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-2023, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* src/backend/parser/analyze.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "access/sysattr.h"
|
|
#include "catalog/pg_proc.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "commands/defrem.h"
|
|
#include "miscadmin.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "nodes/queryjumble.h"
|
|
#include "optimizer/optimizer.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_expr.h"
|
|
#include "parser/parse_func.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/parsetree.h"
|
|
#include "rewrite/rewriteManip.h"
|
|
#include "utils/backend_status.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/guc.h"
|
|
#include "utils/rel.h"
|
|
#include "utils/syscache.h"
|
|
|
|
|
|
/* Hook for plugins to get control at end of parse analysis */
|
|
post_parse_analyze_hook_type post_parse_analyze_hook = NULL;
|
|
|
|
static Query *transformOptionalSelectInto(ParseState *pstate, Node *parseTree);
|
|
static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
|
|
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
|
|
static OnConflictExpr *transformOnConflictClause(ParseState *pstate,
|
|
OnConflictClause *onConflictClause);
|
|
static int count_rowexpr_columns(ParseState *pstate, Node *expr);
|
|
static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
|
|
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 *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt);
|
|
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
|
|
static List *transformReturningList(ParseState *pstate, List *returningList);
|
|
static Query *transformPLAssignStmt(ParseState *pstate,
|
|
PLAssignStmt *stmt);
|
|
static Query *transformDeclareCursorStmt(ParseState *pstate,
|
|
DeclareCursorStmt *stmt);
|
|
static Query *transformExplainStmt(ParseState *pstate,
|
|
ExplainStmt *stmt);
|
|
static Query *transformCreateTableAsStmt(ParseState *pstate,
|
|
CreateTableAsStmt *stmt);
|
|
static Query *transformCallStmt(ParseState *pstate,
|
|
CallStmt *stmt);
|
|
static void transformLockingClause(ParseState *pstate, Query *qry,
|
|
LockingClause *lc, bool pushedDown);
|
|
#ifdef RAW_EXPRESSION_COVERAGE_TEST
|
|
static bool test_raw_expression_coverage(Node *node, void *context);
|
|
#endif
|
|
|
|
|
|
/*
|
|
* parse_analyze_fixedparams
|
|
* 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_fixedparams(RawStmt *parseTree, const char *sourceText,
|
|
const Oid *paramTypes, int numParams,
|
|
QueryEnvironment *queryEnv)
|
|
{
|
|
ParseState *pstate = make_parsestate(NULL);
|
|
Query *query;
|
|
JumbleState *jstate = NULL;
|
|
|
|
Assert(sourceText != NULL); /* required as of 8.4 */
|
|
|
|
pstate->p_sourcetext = sourceText;
|
|
|
|
if (numParams > 0)
|
|
setup_parse_fixed_parameters(pstate, paramTypes, numParams);
|
|
|
|
pstate->p_queryEnv = queryEnv;
|
|
|
|
query = transformTopLevelStmt(pstate, parseTree);
|
|
|
|
if (IsQueryIdEnabled())
|
|
jstate = JumbleQuery(query);
|
|
|
|
if (post_parse_analyze_hook)
|
|
(*post_parse_analyze_hook) (pstate, query, jstate);
|
|
|
|
free_parsestate(pstate);
|
|
|
|
pgstat_report_query_id(query->queryId, false);
|
|
|
|
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(RawStmt *parseTree, const char *sourceText,
|
|
Oid **paramTypes, int *numParams,
|
|
QueryEnvironment *queryEnv)
|
|
{
|
|
ParseState *pstate = make_parsestate(NULL);
|
|
Query *query;
|
|
JumbleState *jstate = NULL;
|
|
|
|
Assert(sourceText != NULL); /* required as of 8.4 */
|
|
|
|
pstate->p_sourcetext = sourceText;
|
|
|
|
setup_parse_variable_parameters(pstate, paramTypes, numParams);
|
|
|
|
pstate->p_queryEnv = queryEnv;
|
|
|
|
query = transformTopLevelStmt(pstate, parseTree);
|
|
|
|
/* make sure all is well with parameter types */
|
|
check_variable_parameters(pstate, query);
|
|
|
|
if (IsQueryIdEnabled())
|
|
jstate = JumbleQuery(query);
|
|
|
|
if (post_parse_analyze_hook)
|
|
(*post_parse_analyze_hook) (pstate, query, jstate);
|
|
|
|
free_parsestate(pstate);
|
|
|
|
pgstat_report_query_id(query->queryId, false);
|
|
|
|
return query;
|
|
}
|
|
|
|
/*
|
|
* parse_analyze_withcb
|
|
*
|
|
* This variant is used when the caller supplies their own parser callback to
|
|
* resolve parameters and possibly other things.
|
|
*/
|
|
Query *
|
|
parse_analyze_withcb(RawStmt *parseTree, const char *sourceText,
|
|
ParserSetupHook parserSetup,
|
|
void *parserSetupArg,
|
|
QueryEnvironment *queryEnv)
|
|
{
|
|
ParseState *pstate = make_parsestate(NULL);
|
|
Query *query;
|
|
JumbleState *jstate = NULL;
|
|
|
|
Assert(sourceText != NULL); /* required as of 8.4 */
|
|
|
|
pstate->p_sourcetext = sourceText;
|
|
pstate->p_queryEnv = queryEnv;
|
|
(*parserSetup) (pstate, parserSetupArg);
|
|
|
|
query = transformTopLevelStmt(pstate, parseTree);
|
|
|
|
if (IsQueryIdEnabled())
|
|
jstate = JumbleQuery(query);
|
|
|
|
if (post_parse_analyze_hook)
|
|
(*post_parse_analyze_hook) (pstate, query, jstate);
|
|
|
|
free_parsestate(pstate);
|
|
|
|
pgstat_report_query_id(query->queryId, false);
|
|
|
|
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;
|
|
|
|
pstate->p_parent_cte = parentCTE;
|
|
pstate->p_locked_from_parent = locked_from_parent;
|
|
pstate->p_resolve_unknowns = resolve_unknowns;
|
|
|
|
query = transformStmt(pstate, parseTree);
|
|
|
|
free_parsestate(pstate);
|
|
|
|
return query;
|
|
}
|
|
|
|
/*
|
|
* transformTopLevelStmt -
|
|
* transform a Parse tree into a Query tree.
|
|
*
|
|
* This function is just responsible for transferring statement location data
|
|
* from the RawStmt into the finished Query.
|
|
*/
|
|
Query *
|
|
transformTopLevelStmt(ParseState *pstate, RawStmt *parseTree)
|
|
{
|
|
Query *result;
|
|
|
|
/* We're at top level, so allow SELECT INTO */
|
|
result = transformOptionalSelectInto(pstate, parseTree->stmt);
|
|
|
|
result->stmt_location = parseTree->stmt_location;
|
|
result->stmt_len = parseTree->stmt_len;
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* transformOptionalSelectInto -
|
|
* If SELECT has INTO, convert it to CREATE TABLE AS.
|
|
*
|
|
* 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.
|
|
*/
|
|
static Query *
|
|
transformOptionalSelectInto(ParseState *pstate, Node *parseTree)
|
|
{
|
|
if (IsA(parseTree, SelectStmt))
|
|
{
|
|
SelectStmt *stmt = (SelectStmt *) parseTree;
|
|
|
|
/* If it's a set-operation tree, drill down to leftmost SelectStmt */
|
|
while (stmt && stmt->op != SETOP_NONE)
|
|
stmt = stmt->larg;
|
|
Assert(stmt && IsA(stmt, SelectStmt) && stmt->larg == NULL);
|
|
|
|
if (stmt->intoClause)
|
|
{
|
|
CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
|
|
|
|
ctas->query = parseTree;
|
|
ctas->into = stmt->intoClause;
|
|
ctas->objtype = OBJECT_TABLE;
|
|
ctas->is_select_into = true;
|
|
|
|
/*
|
|
* Remove the intoClause from the SelectStmt. This makes it safe
|
|
* for transformSelectStmt to complain if it finds intoClause set
|
|
* (implying that the INTO appeared in a disallowed place).
|
|
*/
|
|
stmt->intoClause = NULL;
|
|
|
|
parseTree = (Node *) ctas;
|
|
}
|
|
}
|
|
|
|
return transformStmt(pstate, parseTree);
|
|
}
|
|
|
|
/*
|
|
* transformStmt -
|
|
* recursively transform a Parse tree into a Query tree.
|
|
*/
|
|
Query *
|
|
transformStmt(ParseState *pstate, Node *parseTree)
|
|
{
|
|
Query *result;
|
|
|
|
/*
|
|
* We apply RAW_EXPRESSION_COVERAGE_TEST testing to basic DML statements;
|
|
* we can't just run it on everything because raw_expression_tree_walker()
|
|
* doesn't claim to handle utility statements.
|
|
*/
|
|
#ifdef RAW_EXPRESSION_COVERAGE_TEST
|
|
switch (nodeTag(parseTree))
|
|
{
|
|
case T_SelectStmt:
|
|
case T_InsertStmt:
|
|
case T_UpdateStmt:
|
|
case T_DeleteStmt:
|
|
case T_MergeStmt:
|
|
(void) test_raw_expression_coverage(parseTree, NULL);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
#endif /* RAW_EXPRESSION_COVERAGE_TEST */
|
|
|
|
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)
|
|
result = transformSelectStmt(pstate, n);
|
|
else
|
|
result = transformSetOperationStmt(pstate, n);
|
|
}
|
|
break;
|
|
|
|
case T_ReturnStmt:
|
|
result = transformReturnStmt(pstate, (ReturnStmt *) parseTree);
|
|
break;
|
|
|
|
case T_PLAssignStmt:
|
|
result = transformPLAssignStmt(pstate,
|
|
(PLAssignStmt *) parseTree);
|
|
break;
|
|
|
|
/*
|
|
* Special cases
|
|
*/
|
|
case T_DeclareCursorStmt:
|
|
result = transformDeclareCursorStmt(pstate,
|
|
(DeclareCursorStmt *) parseTree);
|
|
break;
|
|
|
|
case T_ExplainStmt:
|
|
result = transformExplainStmt(pstate,
|
|
(ExplainStmt *) parseTree);
|
|
break;
|
|
|
|
case T_CreateTableAsStmt:
|
|
result = transformCreateTableAsStmt(pstate,
|
|
(CreateTableAsStmt *) parseTree);
|
|
break;
|
|
|
|
case T_CallStmt:
|
|
result = transformCallStmt(pstate,
|
|
(CallStmt *) 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;
|
|
}
|
|
|
|
/* Mark as original query until we learn differently */
|
|
result->querySource = QSRC_ORIGINAL;
|
|
result->canSetTag = true;
|
|
|
|
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().
|
|
*/
|
|
bool
|
|
analyze_requires_snapshot(RawStmt *parseTree)
|
|
{
|
|
bool result;
|
|
|
|
switch (nodeTag(parseTree->stmt))
|
|
{
|
|
/*
|
|
* Optimizable statements
|
|
*/
|
|
case T_InsertStmt:
|
|
case T_DeleteStmt:
|
|
case T_UpdateStmt:
|
|
case T_MergeStmt:
|
|
case T_SelectStmt:
|
|
case T_PLAssignStmt:
|
|
result = true;
|
|
break;
|
|
|
|
/*
|
|
* Special cases
|
|
*/
|
|
case T_DeclareCursorStmt:
|
|
case T_ExplainStmt:
|
|
case T_CreateTableAsStmt:
|
|
/* yes, because we must analyze the contained statement */
|
|
result = true;
|
|
break;
|
|
|
|
default:
|
|
/* other utility statements don't have any real parse analysis */
|
|
result = false;
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* transformDeleteStmt -
|
|
* transforms a Delete Statement
|
|
*/
|
|
static Query *
|
|
transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
|
|
{
|
|
Query *qry = makeNode(Query);
|
|
ParseNamespaceItem *nsitem;
|
|
Node *qual;
|
|
|
|
qry->commandType = CMD_DELETE;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* set up range table with just the result rel */
|
|
qry->resultRelation = setTargetTable(pstate, stmt->relation,
|
|
stmt->relation->inh,
|
|
true,
|
|
ACL_DELETE);
|
|
nsitem = pstate->p_target_nsitem;
|
|
|
|
/* there's no DISTINCT in DELETE */
|
|
qry->distinctClause = NIL;
|
|
|
|
/* subqueries in USING cannot access the result relation */
|
|
nsitem->p_lateral_only = true;
|
|
nsitem->p_lateral_ok = false;
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
transformFromClause(pstate, stmt->usingClause);
|
|
|
|
/* remaining clauses can reference the result relation normally */
|
|
nsitem->p_lateral_only = false;
|
|
nsitem->p_lateral_ok = true;
|
|
|
|
qual = transformWhereClause(pstate, stmt->whereClause,
|
|
EXPR_KIND_WHERE, "WHERE");
|
|
|
|
qry->returningList = transformReturningList(pstate, stmt->returningList);
|
|
|
|
/* done building the range table and jointree */
|
|
qry->rtable = pstate->p_rtable;
|
|
qry->rteperminfos = pstate->p_rteperminfos;
|
|
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
|
|
|
|
qry->hasSubLinks = pstate->p_hasSubLinks;
|
|
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
|
|
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
|
|
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);
|
|
|
|
return qry;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
List *sub_rtable;
|
|
List *sub_rteperminfos;
|
|
List *sub_namespace;
|
|
List *icolumns;
|
|
List *attrnos;
|
|
ParseNamespaceItem *nsitem;
|
|
RTEPermissionInfo *perminfo;
|
|
ListCell *icols;
|
|
ListCell *attnos;
|
|
ListCell *lc;
|
|
bool isOnConflictUpdate;
|
|
AclMode targetPerms;
|
|
|
|
/* There can't be any outer WITH to worry about */
|
|
Assert(pstate->p_ctenamespace == NIL);
|
|
|
|
qry->commandType = CMD_INSERT;
|
|
pstate->p_is_insert = true;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
qry->override = stmt->override;
|
|
|
|
isOnConflictUpdate = (stmt->onConflictClause &&
|
|
stmt->onConflictClause->action == ONCONFLICT_UPDATE);
|
|
|
|
/*
|
|
* 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/rteperminfos/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_rteperminfos = pstate->p_rteperminfos;
|
|
pstate->p_rteperminfos = NIL;
|
|
sub_namespace = pstate->p_namespace;
|
|
pstate->p_namespace = NIL;
|
|
}
|
|
else
|
|
{
|
|
sub_rtable = NIL; /* not used, but keep compiler quiet */
|
|
sub_rteperminfos = NIL;
|
|
sub_namespace = 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.
|
|
*/
|
|
targetPerms = ACL_INSERT;
|
|
if (isOnConflictUpdate)
|
|
targetPerms |= ACL_UPDATE;
|
|
qry->resultRelation = setTargetTable(pstate, stmt->relation,
|
|
false, false, targetPerms);
|
|
|
|
/* Validate stmt->cols list, or build default list if no list given */
|
|
icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos);
|
|
Assert(list_length(icolumns) == list_length(attrnos));
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/*
|
|
* 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...)
|
|
*
|
|
* The sole exception is that we prevent resolving unknown-type
|
|
* outputs as TEXT. This does not change the semantics since if the
|
|
* column type matters semantically, it would have been resolved to
|
|
* something else anyway. Doing this lets us resolve such outputs as
|
|
* the target column's type, which we handle below.
|
|
*/
|
|
sub_pstate->p_rtable = sub_rtable;
|
|
sub_pstate->p_rteperminfos = sub_rteperminfos;
|
|
sub_pstate->p_joinexprs = NIL; /* sub_rtable has no joins */
|
|
sub_pstate->p_nullingrels = NIL;
|
|
sub_pstate->p_namespace = sub_namespace;
|
|
sub_pstate->p_resolve_unknowns = false;
|
|
|
|
selectQuery = transformStmt(sub_pstate, stmt->selectStmt);
|
|
|
|
free_parsestate(sub_pstate);
|
|
|
|
/* The grammar should have produced a SELECT */
|
|
if (!IsA(selectQuery, Query) ||
|
|
selectQuery->commandType != CMD_SELECT)
|
|
elog(ERROR, "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 (but not the namespace).
|
|
*/
|
|
nsitem = addRangeTableEntryForSubquery(pstate,
|
|
selectQuery,
|
|
makeAlias("*SELECT*", NIL),
|
|
false,
|
|
false);
|
|
addNSItemToQuery(pstate, nsitem, true, false, false);
|
|
|
|
/*----------
|
|
* 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
|
|
*----------
|
|
*/
|
|
exprList = NIL;
|
|
foreach(lc, selectQuery->targetList)
|
|
{
|
|
TargetEntry *tle = (TargetEntry *) lfirst(lc);
|
|
Expr *expr;
|
|
|
|
if (tle->resjunk)
|
|
continue;
|
|
if (tle->expr &&
|
|
(IsA(tle->expr, Const) || IsA(tle->expr, Param)) &&
|
|
exprType((Node *) tle->expr) == UNKNOWNOID)
|
|
expr = tle->expr;
|
|
else
|
|
{
|
|
Var *var = makeVarFromTargetEntry(nsitem->p_rtindex, tle);
|
|
|
|
var->location = exprLocation((Node *) tle->expr);
|
|
expr = (Expr *) var;
|
|
}
|
|
exprList = lappend(exprList, expr);
|
|
}
|
|
|
|
/* Prepare row for assignment to target table */
|
|
exprList = transformInsertRow(pstate, exprList,
|
|
stmt->cols,
|
|
icolumns, attrnos,
|
|
false);
|
|
}
|
|
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 *coltypes = NIL;
|
|
List *coltypmods = NIL;
|
|
List *colcollations = NIL;
|
|
int sublist_length = -1;
|
|
bool lateral = false;
|
|
|
|
Assert(selectStmt->intoClause == NULL);
|
|
|
|
foreach(lc, selectStmt->valuesLists)
|
|
{
|
|
List *sublist = (List *) lfirst(lc);
|
|
|
|
/*
|
|
* Do basic expression transformation (same as a ROW() expr, but
|
|
* allow SetToDefault at top level)
|
|
*/
|
|
sublist = transformExpressionList(pstate, sublist,
|
|
EXPR_KIND_VALUES, true);
|
|
|
|
/*
|
|
* 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))));
|
|
}
|
|
|
|
/*
|
|
* Prepare row for assignment to target table. We process any
|
|
* indirection on the target column specs normally but then strip
|
|
* off the resulting field/array assignment nodes, since we don't
|
|
* want the parsed statement to contain copies of those in each
|
|
* VALUES row. (It's annoying to have to transform the
|
|
* indirection specs over and over like this, but avoiding it
|
|
* would take some really messy refactoring of
|
|
* transformAssignmentIndirection.)
|
|
*/
|
|
sublist = transformInsertRow(pstate, sublist,
|
|
stmt->cols,
|
|
icolumns, attrnos,
|
|
true);
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
/*
|
|
* Construct column type/typmod/collation lists for the VALUES RTE.
|
|
* Every expression in each column has been coerced to the type/typmod
|
|
* of the corresponding target column or subfield, so it's sufficient
|
|
* to look at the exprType/exprTypmod of the first row. We don't care
|
|
* about the collation labeling, so just fill in InvalidOid for that.
|
|
*/
|
|
foreach(lc, (List *) linitial(exprsLists))
|
|
{
|
|
Node *val = (Node *) lfirst(lc);
|
|
|
|
coltypes = lappend_oid(coltypes, exprType(val));
|
|
coltypmods = lappend_int(coltypmods, exprTypmod(val));
|
|
colcollations = lappend_oid(colcollations, InvalidOid);
|
|
}
|
|
|
|
/*
|
|
* Ordinarily there can't be any current-level Vars in the expression
|
|
* lists, because the namespace was empty ... but if we're inside
|
|
* CREATE RULE, then NEW/OLD references might appear. In that case we
|
|
* have to mark the VALUES RTE as LATERAL.
|
|
*/
|
|
if (list_length(pstate->p_rtable) != 1 &&
|
|
contain_vars_of_level((Node *) exprsLists, 0))
|
|
lateral = true;
|
|
|
|
/*
|
|
* Generate the VALUES RTE
|
|
*/
|
|
nsitem = addRangeTableEntryForValues(pstate, exprsLists,
|
|
coltypes, coltypmods, colcollations,
|
|
NULL, lateral, true);
|
|
addNSItemToQuery(pstate, nsitem, true, false, false);
|
|
|
|
/*
|
|
* Generate list of Vars referencing the RTE
|
|
*/
|
|
exprList = expandNSItemVars(pstate, nsitem, 0, -1, NULL);
|
|
|
|
/*
|
|
* Re-apply any indirection on the target column specs to the Vars
|
|
*/
|
|
exprList = transformInsertRow(pstate, exprList,
|
|
stmt->cols,
|
|
icolumns, attrnos,
|
|
false);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Process INSERT ... VALUES with a single VALUES sublist. We treat
|
|
* this case separately for efficiency. The sublist is just computed
|
|
* directly as the Query's targetlist, with no VALUES RTE. So it
|
|
* works just like a SELECT without any FROM.
|
|
*/
|
|
List *valuesLists = selectStmt->valuesLists;
|
|
|
|
Assert(list_length(valuesLists) == 1);
|
|
Assert(selectStmt->intoClause == NULL);
|
|
|
|
/*
|
|
* Do basic expression transformation (same as a ROW() expr, but allow
|
|
* SetToDefault at top level)
|
|
*/
|
|
exprList = transformExpressionList(pstate,
|
|
(List *) linitial(valuesLists),
|
|
EXPR_KIND_VALUES_SINGLE,
|
|
true);
|
|
|
|
/* Prepare row for assignment to target table */
|
|
exprList = transformInsertRow(pstate, exprList,
|
|
stmt->cols,
|
|
icolumns, attrnos,
|
|
false);
|
|
}
|
|
|
|
/*
|
|
* Generate query's target list using the computed list of expressions.
|
|
* Also, mark all the target columns as needing insert permissions.
|
|
*/
|
|
perminfo = pstate->p_target_nsitem->p_perminfo;
|
|
qry->targetList = NIL;
|
|
Assert(list_length(exprList) <= list_length(icolumns));
|
|
forthree(lc, exprList, icols, icolumns, attnos, attrnos)
|
|
{
|
|
Expr *expr = (Expr *) lfirst(lc);
|
|
ResTarget *col = lfirst_node(ResTarget, icols);
|
|
AttrNumber attr_num = (AttrNumber) lfirst_int(attnos);
|
|
TargetEntry *tle;
|
|
|
|
tle = makeTargetEntry(expr,
|
|
attr_num,
|
|
col->name,
|
|
false);
|
|
qry->targetList = lappend(qry->targetList, tle);
|
|
|
|
perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
|
|
attr_num - FirstLowInvalidHeapAttributeNumber);
|
|
}
|
|
|
|
/*
|
|
* If we have any clauses yet to process, set the query namespace to
|
|
* contain only the target relation, removing any entries added in a
|
|
* sub-SELECT or VALUES list.
|
|
*/
|
|
if (stmt->onConflictClause || stmt->returningList)
|
|
{
|
|
pstate->p_namespace = NIL;
|
|
addNSItemToQuery(pstate, pstate->p_target_nsitem,
|
|
false, true, true);
|
|
}
|
|
|
|
/* Process ON CONFLICT, if any. */
|
|
if (stmt->onConflictClause)
|
|
qry->onConflict = transformOnConflictClause(pstate,
|
|
stmt->onConflictClause);
|
|
|
|
/* Process RETURNING, if any. */
|
|
if (stmt->returningList)
|
|
qry->returningList = transformReturningList(pstate,
|
|
stmt->returningList);
|
|
|
|
/* done building the range table and jointree */
|
|
qry->rtable = pstate->p_rtable;
|
|
qry->rteperminfos = pstate->p_rteperminfos;
|
|
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
|
|
|
|
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
|
|
qry->hasSubLinks = pstate->p_hasSubLinks;
|
|
|
|
assign_query_collations(pstate, qry);
|
|
|
|
return qry;
|
|
}
|
|
|
|
/*
|
|
* Prepare an INSERT row for assignment to the target table.
|
|
*
|
|
* exprlist: transformed expressions for source values; these might come from
|
|
* a VALUES row, or be Vars referencing a sub-SELECT or VALUES RTE output.
|
|
* stmtcols: original target-columns spec for INSERT (we just test for NIL)
|
|
* icolumns: effective target-columns spec (list of ResTarget)
|
|
* attrnos: integer column numbers (must be same length as icolumns)
|
|
* strip_indirection: if true, remove any field/array assignment nodes
|
|
*/
|
|
List *
|
|
transformInsertRow(ParseState *pstate, List *exprlist,
|
|
List *stmtcols, List *icolumns, List *attrnos,
|
|
bool strip_indirection)
|
|
{
|
|
List *result;
|
|
ListCell *lc;
|
|
ListCell *icols;
|
|
ListCell *attnos;
|
|
|
|
/*
|
|
* 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(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, 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(list_nth(icolumns,
|
|
list_length(exprlist))))));
|
|
}
|
|
|
|
/*
|
|
* Prepare columns for assignment to target table.
|
|
*/
|
|
result = NIL;
|
|
forthree(lc, exprlist, icols, icolumns, attnos, attrnos)
|
|
{
|
|
Expr *expr = (Expr *) lfirst(lc);
|
|
ResTarget *col = lfirst_node(ResTarget, icols);
|
|
int attno = lfirst_int(attnos);
|
|
|
|
expr = transformAssignedExpr(pstate, expr,
|
|
EXPR_KIND_INSERT_TARGET,
|
|
col->name,
|
|
attno,
|
|
col->indirection,
|
|
col->location);
|
|
|
|
if (strip_indirection)
|
|
{
|
|
while (expr)
|
|
{
|
|
if (IsA(expr, FieldStore))
|
|
{
|
|
FieldStore *fstore = (FieldStore *) expr;
|
|
|
|
expr = (Expr *) linitial(fstore->newvals);
|
|
}
|
|
else if (IsA(expr, SubscriptingRef))
|
|
{
|
|
SubscriptingRef *sbsref = (SubscriptingRef *) expr;
|
|
|
|
if (sbsref->refassgnexpr == NULL)
|
|
break;
|
|
|
|
expr = sbsref->refassgnexpr;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
result = lappend(result, expr);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* transformOnConflictClause -
|
|
* transforms an OnConflictClause in an INSERT
|
|
*/
|
|
static OnConflictExpr *
|
|
transformOnConflictClause(ParseState *pstate,
|
|
OnConflictClause *onConflictClause)
|
|
{
|
|
ParseNamespaceItem *exclNSItem = NULL;
|
|
List *arbiterElems;
|
|
Node *arbiterWhere;
|
|
Oid arbiterConstraint;
|
|
List *onConflictSet = NIL;
|
|
Node *onConflictWhere = NULL;
|
|
int exclRelIndex = 0;
|
|
List *exclRelTlist = NIL;
|
|
OnConflictExpr *result;
|
|
|
|
/*
|
|
* If this is ON CONFLICT ... UPDATE, first create the range table entry
|
|
* for the EXCLUDED pseudo relation, so that that will be present while
|
|
* processing arbiter expressions. (You can't actually reference it from
|
|
* there, but this provides a useful error message if you try.)
|
|
*/
|
|
if (onConflictClause->action == ONCONFLICT_UPDATE)
|
|
{
|
|
Relation targetrel = pstate->p_target_relation;
|
|
RangeTblEntry *exclRte;
|
|
|
|
exclNSItem = addRangeTableEntryForRelation(pstate,
|
|
targetrel,
|
|
RowExclusiveLock,
|
|
makeAlias("excluded", NIL),
|
|
false, false);
|
|
exclRte = exclNSItem->p_rte;
|
|
exclRelIndex = exclNSItem->p_rtindex;
|
|
|
|
/*
|
|
* relkind is set to composite to signal that we're not dealing with
|
|
* an actual relation, and no permission checks are required on it.
|
|
* (We'll check the actual target relation, instead.)
|
|
*/
|
|
exclRte->relkind = RELKIND_COMPOSITE_TYPE;
|
|
|
|
/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
|
|
exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
|
|
exclRelIndex);
|
|
}
|
|
|
|
/* Process the arbiter clause, ON CONFLICT ON (...) */
|
|
transformOnConflictArbiter(pstate, onConflictClause, &arbiterElems,
|
|
&arbiterWhere, &arbiterConstraint);
|
|
|
|
/* Process DO UPDATE */
|
|
if (onConflictClause->action == ONCONFLICT_UPDATE)
|
|
{
|
|
/*
|
|
* Expressions in the UPDATE targetlist need to be handled like UPDATE
|
|
* not INSERT. We don't need to save/restore this because all INSERT
|
|
* expressions have been parsed already.
|
|
*/
|
|
pstate->p_is_insert = false;
|
|
|
|
/*
|
|
* Add the EXCLUDED pseudo relation to the query namespace, making it
|
|
* available in the UPDATE subexpressions.
|
|
*/
|
|
addNSItemToQuery(pstate, exclNSItem, false, true, true);
|
|
|
|
/*
|
|
* Now transform the UPDATE subexpressions.
|
|
*/
|
|
onConflictSet =
|
|
transformUpdateTargetList(pstate, onConflictClause->targetList);
|
|
|
|
onConflictWhere = transformWhereClause(pstate,
|
|
onConflictClause->whereClause,
|
|
EXPR_KIND_WHERE, "WHERE");
|
|
|
|
/*
|
|
* Remove the EXCLUDED pseudo relation from the query namespace, since
|
|
* it's not supposed to be available in RETURNING. (Maybe someday we
|
|
* could allow that, and drop this step.)
|
|
*/
|
|
Assert((ParseNamespaceItem *) llast(pstate->p_namespace) == exclNSItem);
|
|
pstate->p_namespace = list_delete_last(pstate->p_namespace);
|
|
}
|
|
|
|
/* Finally, build ON CONFLICT DO [NOTHING | UPDATE] expression */
|
|
result = makeNode(OnConflictExpr);
|
|
|
|
result->action = onConflictClause->action;
|
|
result->arbiterElems = arbiterElems;
|
|
result->arbiterWhere = arbiterWhere;
|
|
result->constraint = arbiterConstraint;
|
|
result->onConflictSet = onConflictSet;
|
|
result->onConflictWhere = onConflictWhere;
|
|
result->exclRelIndex = exclRelIndex;
|
|
result->exclRelTlist = exclRelTlist;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
* BuildOnConflictExcludedTargetlist
|
|
* Create target list for the EXCLUDED pseudo-relation of ON CONFLICT,
|
|
* representing the columns of targetrel with varno exclRelIndex.
|
|
*
|
|
* Note: Exported for use in the rewriter.
|
|
*/
|
|
List *
|
|
BuildOnConflictExcludedTargetlist(Relation targetrel,
|
|
Index exclRelIndex)
|
|
{
|
|
List *result = NIL;
|
|
int attno;
|
|
Var *var;
|
|
TargetEntry *te;
|
|
|
|
/*
|
|
* 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 = TupleDescAttr(targetrel->rd_att, attno);
|
|
char *name;
|
|
|
|
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);
|
|
name = NULL;
|
|
}
|
|
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;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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;
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
Query *qry = makeNode(Query);
|
|
Node *qual;
|
|
ListCell *l;
|
|
|
|
qry->commandType = CMD_SELECT;
|
|
|
|
/* 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);
|
|
|
|
/* transform targetlist */
|
|
qry->targetList = transformTargetList(pstate, stmt->targetList,
|
|
EXPR_KIND_SELECT_TARGET);
|
|
|
|
/* mark column origins */
|
|
markTargetListOrigins(pstate, qry->targetList);
|
|
|
|
/* transform WHERE */
|
|
qual = transformWhereClause(pstate, stmt->whereClause,
|
|
EXPR_KIND_WHERE, "WHERE");
|
|
|
|
/* initial processing of HAVING clause is much like WHERE clause */
|
|
qry->havingQual = transformWhereClause(pstate, stmt->havingClause,
|
|
EXPR_KIND_HAVING, "HAVING");
|
|
|
|
/*
|
|
* 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,
|
|
false /* allow SQL92 rules */ );
|
|
|
|
qry->groupClause = transformGroupClause(pstate,
|
|
stmt->groupClause,
|
|
&qry->groupingSets,
|
|
&qry->targetList,
|
|
qry->sortClause,
|
|
EXPR_KIND_GROUP_BY,
|
|
false /* allow SQL92 rules */ );
|
|
qry->groupDistinct = stmt->groupDistinct;
|
|
|
|
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",
|
|
stmt->limitOption);
|
|
qry->limitCount = transformLimitClause(pstate, stmt->limitCount,
|
|
EXPR_KIND_LIMIT, "LIMIT",
|
|
stmt->limitOption);
|
|
qry->limitOption = stmt->limitOption;
|
|
|
|
/* 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->rteperminfos = pstate->p_rteperminfos;
|
|
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
|
|
|
|
qry->hasSubLinks = pstate->p_hasSubLinks;
|
|
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
|
|
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
|
|
qry->hasAggs = pstate->p_hasAggs;
|
|
|
|
foreach(l, stmt->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;
|
|
}
|
|
|
|
/*
|
|
* 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 *coltypes = NIL;
|
|
List *coltypmods = NIL;
|
|
List *colcollations = NIL;
|
|
List **colexprs = NULL;
|
|
int sublist_length = -1;
|
|
bool lateral = false;
|
|
ParseNamespaceItem *nsitem;
|
|
ListCell *lc;
|
|
ListCell *lc2;
|
|
int i;
|
|
|
|
qry->commandType = CMD_SELECT;
|
|
|
|
/* Most SELECT stuff doesn't apply in a VALUES clause */
|
|
Assert(stmt->distinctClause == NIL);
|
|
Assert(stmt->intoClause == NULL);
|
|
Assert(stmt->targetList == NIL);
|
|
Assert(stmt->fromClause == NIL);
|
|
Assert(stmt->whereClause == NULL);
|
|
Assert(stmt->groupClause == NIL);
|
|
Assert(stmt->havingClause == NULL);
|
|
Assert(stmt->windowClause == NIL);
|
|
Assert(stmt->op == SETOP_NONE);
|
|
|
|
/* 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.
|
|
*
|
|
* 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, but here
|
|
* we disallow SetToDefault)
|
|
*/
|
|
sublist = transformExpressionList(pstate, sublist,
|
|
EXPR_KIND_VALUES, false);
|
|
|
|
/*
|
|
* 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))));
|
|
}
|
|
|
|
/* Build per-column expression lists */
|
|
i = 0;
|
|
foreach(lc2, sublist)
|
|
{
|
|
Node *col = (Node *) lfirst(lc2);
|
|
|
|
colexprs[i] = lappend(colexprs[i], col);
|
|
i++;
|
|
}
|
|
|
|
/* Release sub-list's cells to save memory */
|
|
list_free(sublist);
|
|
|
|
/* Prepare an exprsLists element for this row */
|
|
exprsLists = lappend(exprsLists, NIL);
|
|
}
|
|
|
|
/*
|
|
* Now resolve the common types of the columns, and coerce everything to
|
|
* those types. Then identify the common typmod and 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.
|
|
*/
|
|
for (i = 0; i < sublist_length; i++)
|
|
{
|
|
Oid coltype;
|
|
int32 coltypmod;
|
|
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;
|
|
}
|
|
|
|
coltypmod = select_common_typmod(pstate, colexprs[i], coltype);
|
|
colcoll = select_common_collation(pstate, colexprs[i], true);
|
|
|
|
coltypes = lappend_oid(coltypes, coltype);
|
|
coltypmods = lappend_int(coltypmods, coltypmod);
|
|
colcollations = lappend_oid(colcollations, colcoll);
|
|
}
|
|
|
|
/*
|
|
* Finally, rearrange the coerced expressions into row-organized lists.
|
|
*/
|
|
for (i = 0; i < sublist_length; i++)
|
|
{
|
|
forboth(lc, colexprs[i], lc2, exprsLists)
|
|
{
|
|
Node *col = (Node *) lfirst(lc);
|
|
List *sublist = lfirst(lc2);
|
|
|
|
sublist = lappend(sublist, col);
|
|
lfirst(lc2) = sublist;
|
|
}
|
|
list_free(colexprs[i]);
|
|
}
|
|
|
|
/*
|
|
* Ordinarily there can't be any current-level Vars in the expression
|
|
* lists, because the namespace was empty ... but if we're inside CREATE
|
|
* RULE, then NEW/OLD references might appear. In that case we have to
|
|
* mark the VALUES RTE as LATERAL.
|
|
*/
|
|
if (pstate->p_rtable != NIL &&
|
|
contain_vars_of_level((Node *) exprsLists, 0))
|
|
lateral = true;
|
|
|
|
/*
|
|
* Generate the VALUES RTE
|
|
*/
|
|
nsitem = addRangeTableEntryForValues(pstate, exprsLists,
|
|
coltypes, coltypmods, colcollations,
|
|
NULL, lateral, true);
|
|
addNSItemToQuery(pstate, nsitem, true, true, true);
|
|
|
|
/*
|
|
* Generate a targetlist as though expanding "*"
|
|
*/
|
|
Assert(pstate->p_next_resno == 1);
|
|
qry->targetList = expandNSItemAttrs(pstate, nsitem, 0, true, -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,
|
|
false /* allow SQL92 rules */ );
|
|
|
|
qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
|
|
EXPR_KIND_OFFSET, "OFFSET",
|
|
stmt->limitOption);
|
|
qry->limitCount = transformLimitClause(pstate, stmt->limitCount,
|
|
EXPR_KIND_LIMIT, "LIMIT",
|
|
stmt->limitOption);
|
|
qry->limitOption = stmt->limitOption;
|
|
|
|
if (stmt->lockingClause)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("%s cannot be applied to VALUES",
|
|
LCS_asString(((LockingClause *)
|
|
linitial(stmt->lockingClause))->strength))));
|
|
|
|
qry->rtable = pstate->p_rtable;
|
|
qry->rteperminfos = pstate->p_rteperminfos;
|
|
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
|
|
|
|
qry->hasSubLinks = pstate->p_hasSubLinks;
|
|
|
|
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;
|
|
int leftmostRTI;
|
|
Query *leftmostQuery;
|
|
SetOperationStmt *sostmt;
|
|
List *sortClause;
|
|
Node *limitOffset;
|
|
Node *limitCount;
|
|
List *lockingClause;
|
|
WithClause *withClause;
|
|
Node *node;
|
|
ListCell *left_tlist,
|
|
*lct,
|
|
*lcm,
|
|
*lcc,
|
|
*l;
|
|
List *targetvars,
|
|
*targetnames,
|
|
*sv_namespace;
|
|
int sv_rtable_length;
|
|
ParseNamespaceItem *jnsitem;
|
|
ParseNamespaceColumn *sortnscolumns;
|
|
int sortcolindex;
|
|
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 && leftmostSelect->op != SETOP_NONE)
|
|
leftmostSelect = leftmostSelect->larg;
|
|
Assert(leftmostSelect && IsA(leftmostSelect, SelectStmt) &&
|
|
leftmostSelect->larg == NULL);
|
|
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)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("%s is not allowed with UNION/INTERSECT/EXCEPT",
|
|
LCS_asString(((LockingClause *)
|
|
linitial(lockingClause))->strength))));
|
|
|
|
/* Process the WITH clause independently of all else */
|
|
if (withClause)
|
|
{
|
|
qry->hasRecursive = withClause->recursive;
|
|
qry->cteList = transformWithClause(pstate, withClause);
|
|
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
|
|
}
|
|
|
|
/*
|
|
* Recursively transform the components of the tree.
|
|
*/
|
|
sostmt = castNode(SetOperationStmt,
|
|
transformSetOperationTree(pstate, stmt, true, NULL));
|
|
Assert(sostmt);
|
|
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;
|
|
Assert(node && IsA(node, RangeTblRef));
|
|
leftmostRTI = ((RangeTblRef *) node)->rtindex;
|
|
leftmostQuery = rt_fetch(leftmostRTI, pstate->p_rtable)->subquery;
|
|
Assert(leftmostQuery != 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;
|
|
sortnscolumns = (ParseNamespaceColumn *)
|
|
palloc0(list_length(sostmt->colTypes) * sizeof(ParseNamespaceColumn));
|
|
sortcolindex = 0;
|
|
|
|
forfour(lct, sostmt->colTypes,
|
|
lcm, sostmt->colTypmods,
|
|
lcc, sostmt->colCollations,
|
|
left_tlist, leftmostQuery->targetList)
|
|
{
|
|
Oid colType = lfirst_oid(lct);
|
|
int32 colTypmod = lfirst_int(lcm);
|
|
Oid colCollation = lfirst_oid(lcc);
|
|
TargetEntry *lefttle = (TargetEntry *) lfirst(left_tlist);
|
|
char *colName;
|
|
TargetEntry *tle;
|
|
Var *var;
|
|
|
|
Assert(!lefttle->resjunk);
|
|
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));
|
|
sortnscolumns[sortcolindex].p_varno = leftmostRTI;
|
|
sortnscolumns[sortcolindex].p_varattno = lefttle->resno;
|
|
sortnscolumns[sortcolindex].p_vartype = colType;
|
|
sortnscolumns[sortcolindex].p_vartypmod = colTypmod;
|
|
sortnscolumns[sortcolindex].p_varcollid = colCollation;
|
|
sortnscolumns[sortcolindex].p_varnosyn = leftmostRTI;
|
|
sortnscolumns[sortcolindex].p_varattnosyn = lefttle->resno;
|
|
sortcolindex++;
|
|
}
|
|
|
|
/*
|
|
* As a first step towards supporting sort clauses that are expressions
|
|
* using the output columns, generate a namespace 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);
|
|
|
|
jnsitem = addRangeTableEntryForJoin(pstate,
|
|
targetnames,
|
|
sortnscolumns,
|
|
JOIN_INNER,
|
|
0,
|
|
targetvars,
|
|
NIL,
|
|
NIL,
|
|
NULL,
|
|
NULL,
|
|
false);
|
|
|
|
sv_namespace = pstate->p_namespace;
|
|
pstate->p_namespace = NIL;
|
|
|
|
/* add jnsitem to column namespace only */
|
|
addNSItemToQuery(pstate, jnsitem, 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 /* allow SQL92 rules */ );
|
|
|
|
/* restore namespace, remove join RTE from rtable */
|
|
pstate->p_namespace = sv_namespace;
|
|
pstate->p_rtable = list_truncate(pstate->p_rtable, sv_rtable_length);
|
|
|
|
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(list_nth(qry->targetList, tllen)))));
|
|
|
|
qry->limitOffset = transformLimitClause(pstate, limitOffset,
|
|
EXPR_KIND_OFFSET, "OFFSET",
|
|
stmt->limitOption);
|
|
qry->limitCount = transformLimitClause(pstate, limitCount,
|
|
EXPR_KIND_LIMIT, "LIMIT",
|
|
stmt->limitOption);
|
|
qry->limitOption = stmt->limitOption;
|
|
|
|
qry->rtable = pstate->p_rtable;
|
|
qry->rteperminfos = pstate->p_rteperminfos;
|
|
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
|
|
|
|
qry->hasSubLinks = pstate->p_hasSubLinks;
|
|
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Make a SortGroupClause node for a SetOperationStmt's groupClauses
|
|
*
|
|
* If require_hash is true, the caller is indicating that they need hash
|
|
* support or they will fail. So look extra hard for hash support.
|
|
*/
|
|
SortGroupClause *
|
|
makeSortGroupClauseForSetOp(Oid rescoltype, bool require_hash)
|
|
{
|
|
SortGroupClause *grpcl = makeNode(SortGroupClause);
|
|
Oid sortop;
|
|
Oid eqop;
|
|
bool hashable;
|
|
|
|
/* determine the eqop and optional sortop */
|
|
get_sort_group_operators(rescoltype,
|
|
false, true, false,
|
|
&sortop, &eqop, NULL,
|
|
&hashable);
|
|
|
|
/*
|
|
* The type cache doesn't believe that record is hashable (see
|
|
* cache_record_field_properties()), but if the caller really needs hash
|
|
* support, we can assume it does. Worst case, if any components of the
|
|
* record don't support hashing, we will fail at execution.
|
|
*/
|
|
if (require_hash && (rescoltype == RECORDOID || rescoltype == RECORDARRAYOID))
|
|
hashable = true;
|
|
|
|
/* 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;
|
|
|
|
return grpcl;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
Assert(stmt && IsA(stmt, SelectStmt));
|
|
|
|
/* 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),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("%s is not allowed with UNION/INTERSECT/EXCEPT",
|
|
LCS_asString(((LockingClause *)
|
|
linitial(stmt->lockingClause))->strength))));
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
Assert(stmt->larg == NULL && stmt->rarg == NULL);
|
|
isLeaf = true;
|
|
}
|
|
else
|
|
{
|
|
Assert(stmt->larg != NULL && stmt->rarg != NULL);
|
|
if (stmt->sortClause || stmt->limitOffset || stmt->limitCount ||
|
|
stmt->lockingClause || stmt->withClause)
|
|
isLeaf = true;
|
|
else
|
|
isLeaf = false;
|
|
}
|
|
|
|
if (isLeaf)
|
|
{
|
|
/* Process leaf SELECT */
|
|
Query *selectQuery;
|
|
char selectName[32];
|
|
ParseNamespaceItem *nsitem;
|
|
RangeTblRef *rtr;
|
|
ListCell *tl;
|
|
|
|
/*
|
|
* Transform SelectStmt into a Query.
|
|
*
|
|
* This works the same as SELECT transformation normally would, except
|
|
* that we prevent resolving unknown-type outputs as TEXT. This does
|
|
* not change the subquery's semantics since if the column type
|
|
* matters semantically, it would have been resolved to something else
|
|
* anyway. Doing this lets us resolve such outputs using
|
|
* select_common_type(), below.
|
|
*
|
|
* 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_namespace)
|
|
{
|
|
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)
|
|
{
|
|
*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.
|
|
*/
|
|
snprintf(selectName, sizeof(selectName), "*SELECT* %d",
|
|
list_length(pstate->p_rtable) + 1);
|
|
nsitem = addRangeTableEntryForSubquery(pstate,
|
|
selectQuery,
|
|
makeAlias(selectName, NIL),
|
|
false,
|
|
false);
|
|
|
|
/*
|
|
* Return a RangeTblRef to replace the SelectStmt in the set-op tree.
|
|
*/
|
|
rtr = makeNode(RangeTblRef);
|
|
rtr->rtindex = nsitem->p_rtindex;
|
|
return (Node *) rtr;
|
|
}
|
|
else
|
|
{
|
|
/* Process an internal node (set operation node) */
|
|
SetOperationStmt *op = makeNode(SetOperationStmt);
|
|
List *ltargetlist;
|
|
List *rtargetlist;
|
|
ListCell *ltl;
|
|
ListCell *rtl;
|
|
const char *context;
|
|
bool recursive = (pstate->p_parent_cte &&
|
|
pstate->p_parent_cte->cterecursive);
|
|
|
|
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,
|
|
<argetlist);
|
|
|
|
/*
|
|
* 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 && recursive)
|
|
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)
|
|
*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);
|
|
Node *bestexpr;
|
|
int bestlocation;
|
|
Oid rescoltype;
|
|
int32 rescoltypmod;
|
|
Oid rescolcoll;
|
|
|
|
/* select common type, same as CASE et al */
|
|
rescoltype = select_common_type(pstate,
|
|
list_make2(lcolnode, rcolnode),
|
|
context,
|
|
&bestexpr);
|
|
bestlocation = exprLocation(bestexpr);
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
rescoltypmod = select_common_typmod(pstate,
|
|
list_make2(lcolnode, rcolnode),
|
|
rescoltype);
|
|
|
|
/*
|
|
* 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 colCollations 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)
|
|
{
|
|
ParseCallbackState pcbstate;
|
|
|
|
setup_parser_errposition_callback(&pcbstate, pstate,
|
|
bestlocation);
|
|
|
|
/*
|
|
* If it's a recursive union, we need to require hashing
|
|
* support.
|
|
*/
|
|
op->groupClauses = lappend(op->groupClauses,
|
|
makeSortGroupClauseForSetOp(rescoltype, recursive));
|
|
|
|
cancel_parser_errposition_callback(&pcbstate);
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
SetToDefault *rescolnode = makeNode(SetToDefault);
|
|
TargetEntry *restle;
|
|
|
|
rescolnode->typeId = rescoltype;
|
|
rescolnode->typeMod = rescoltypmod;
|
|
rescolnode->collation = rescolcoll;
|
|
rescolnode->location = bestlocation;
|
|
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;
|
|
int leftmostRTI;
|
|
Query *leftmostQuery;
|
|
List *targetList;
|
|
ListCell *left_tlist;
|
|
ListCell *nrtl;
|
|
int next_resno;
|
|
|
|
/*
|
|
* Find leftmost leaf SELECT
|
|
*/
|
|
node = larg;
|
|
while (node && IsA(node, SetOperationStmt))
|
|
node = ((SetOperationStmt *) node)->larg;
|
|
Assert(node && IsA(node, RangeTblRef));
|
|
leftmostRTI = ((RangeTblRef *) node)->rtindex;
|
|
leftmostQuery = rt_fetch(leftmostRTI, pstate->p_rtable)->subquery;
|
|
Assert(leftmostQuery != NULL);
|
|
|
|
/*
|
|
* Generate dummy targetlist using column names of leftmost select and
|
|
* dummy result expressions of the non-recursive term.
|
|
*/
|
|
targetList = NIL;
|
|
next_resno = 1;
|
|
|
|
forboth(nrtl, nrtargetlist, left_tlist, leftmostQuery->targetList)
|
|
{
|
|
TargetEntry *nrtle = (TargetEntry *) lfirst(nrtl);
|
|
TargetEntry *lefttle = (TargetEntry *) lfirst(left_tlist);
|
|
char *colName;
|
|
TargetEntry *tle;
|
|
|
|
Assert(!lefttle->resjunk);
|
|
colName = pstrdup(lefttle->resname);
|
|
tle = makeTargetEntry(nrtle->expr,
|
|
next_resno++,
|
|
colName,
|
|
false);
|
|
targetList = lappend(targetList, tle);
|
|
}
|
|
|
|
/* Now build CTE's output column info using dummy targetlist */
|
|
analyzeCTETargetList(pstate, pstate->p_parent_cte, targetList);
|
|
}
|
|
|
|
|
|
/*
|
|
* transformReturnStmt -
|
|
* transforms a return statement
|
|
*/
|
|
static Query *
|
|
transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
|
|
{
|
|
Query *qry = makeNode(Query);
|
|
|
|
qry->commandType = CMD_SELECT;
|
|
qry->isReturn = true;
|
|
|
|
qry->targetList = list_make1(makeTargetEntry((Expr *) transformExpr(pstate, stmt->returnval, EXPR_KIND_SELECT_TARGET),
|
|
1, NULL, false));
|
|
|
|
if (pstate->p_resolve_unknowns)
|
|
resolveTargetListUnknowns(pstate, qry->targetList);
|
|
qry->rtable = pstate->p_rtable;
|
|
qry->rteperminfos = pstate->p_rteperminfos;
|
|
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
|
|
qry->hasSubLinks = pstate->p_hasSubLinks;
|
|
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
|
|
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
|
|
qry->hasAggs = pstate->p_hasAggs;
|
|
|
|
assign_query_collations(pstate, qry);
|
|
|
|
return qry;
|
|
}
|
|
|
|
|
|
/*
|
|
* transformUpdateStmt -
|
|
* transforms an update statement
|
|
*/
|
|
static Query *
|
|
transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
|
|
{
|
|
Query *qry = makeNode(Query);
|
|
ParseNamespaceItem *nsitem;
|
|
Node *qual;
|
|
|
|
qry->commandType = CMD_UPDATE;
|
|
pstate->p_is_insert = false;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
qry->resultRelation = setTargetTable(pstate, stmt->relation,
|
|
stmt->relation->inh,
|
|
true,
|
|
ACL_UPDATE);
|
|
nsitem = pstate->p_target_nsitem;
|
|
|
|
/* subqueries in FROM cannot access the result relation */
|
|
nsitem->p_lateral_only = true;
|
|
nsitem->p_lateral_ok = 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);
|
|
|
|
/* remaining clauses can reference the result relation normally */
|
|
nsitem->p_lateral_only = false;
|
|
nsitem->p_lateral_ok = true;
|
|
|
|
qual = transformWhereClause(pstate, stmt->whereClause,
|
|
EXPR_KIND_WHERE, "WHERE");
|
|
|
|
qry->returningList = transformReturningList(pstate, stmt->returningList);
|
|
|
|
/*
|
|
* 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, stmt->targetList);
|
|
|
|
qry->rtable = pstate->p_rtable;
|
|
qry->rteperminfos = pstate->p_rteperminfos;
|
|
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
|
|
|
|
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
|
|
qry->hasSubLinks = pstate->p_hasSubLinks;
|
|
|
|
assign_query_collations(pstate, qry);
|
|
|
|
return qry;
|
|
}
|
|
|
|
/*
|
|
* transformUpdateTargetList -
|
|
* handle SET clause in UPDATE/MERGE/INSERT ... ON CONFLICT UPDATE
|
|
*/
|
|
List *
|
|
transformUpdateTargetList(ParseState *pstate, List *origTlist)
|
|
{
|
|
List *tlist = NIL;
|
|
RTEPermissionInfo *target_perminfo;
|
|
ListCell *orig_tl;
|
|
ListCell *tl;
|
|
|
|
tlist = transformTargetList(pstate, origTlist,
|
|
EXPR_KIND_UPDATE_SOURCE);
|
|
|
|
/* Prepare to assign non-conflicting resnos to resjunk attributes */
|
|
if (pstate->p_next_resno <= RelationGetNumberOfAttributes(pstate->p_target_relation))
|
|
pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
|
|
|
|
/* Prepare non-junk columns for assignment to target table */
|
|
target_perminfo = pstate->p_target_nsitem->p_perminfo;
|
|
orig_tl = list_head(origTlist);
|
|
|
|
foreach(tl, tlist)
|
|
{
|
|
TargetEntry *tle = (TargetEntry *) lfirst(tl);
|
|
ResTarget *origTarget;
|
|
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;
|
|
}
|
|
if (orig_tl == NULL)
|
|
elog(ERROR, "UPDATE target count mismatch --- internal error");
|
|
origTarget = lfirst_node(ResTarget, orig_tl);
|
|
|
|
attrno = attnameAttNum(pstate->p_target_relation,
|
|
origTarget->name, true);
|
|
if (attrno == InvalidAttrNumber)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
|
errmsg("column \"%s\" of relation \"%s\" does not exist",
|
|
origTarget->name,
|
|
RelationGetRelationName(pstate->p_target_relation)),
|
|
parser_errposition(pstate, origTarget->location)));
|
|
|
|
updateTargetListEntry(pstate, tle, origTarget->name,
|
|
attrno,
|
|
origTarget->indirection,
|
|
origTarget->location);
|
|
|
|
/* Mark the target column as requiring update permissions */
|
|
target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
|
|
attrno - FirstLowInvalidHeapAttributeNumber);
|
|
|
|
orig_tl = lnext(origTlist, orig_tl);
|
|
}
|
|
if (orig_tl != NULL)
|
|
elog(ERROR, "UPDATE target count mismatch --- internal error");
|
|
|
|
return tlist;
|
|
}
|
|
|
|
/*
|
|
* transformReturningList -
|
|
* handle a RETURNING clause in INSERT/UPDATE/DELETE
|
|
*/
|
|
static List *
|
|
transformReturningList(ParseState *pstate, List *returningList)
|
|
{
|
|
List *rlist;
|
|
int save_next_resno;
|
|
|
|
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;
|
|
|
|
/* transform RETURNING identically to a SELECT targetlist */
|
|
rlist = transformTargetList(pstate, returningList, EXPR_KIND_RETURNING);
|
|
|
|
/*
|
|
* Complain if the nonempty tlist expanded to nothing (which is possible
|
|
* if it contains only a star-expansion of a zero-column table). If we
|
|
* allow this, the parsed Query will look like it didn't have RETURNING,
|
|
* with results that would probably surprise the user.
|
|
*/
|
|
if (rlist == NIL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("RETURNING must have at least one column"),
|
|
parser_errposition(pstate,
|
|
exprLocation(linitial(returningList)))));
|
|
|
|
/* 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;
|
|
|
|
return rlist;
|
|
}
|
|
|
|
|
|
/*
|
|
* transformPLAssignStmt -
|
|
* transform a PL/pgSQL assignment statement
|
|
*
|
|
* If there is no opt_indirection, the transformed statement looks like
|
|
* "SELECT a_expr ...", except the expression has been cast to the type of
|
|
* the target. With indirection, it's still a SELECT, but the expression will
|
|
* incorporate FieldStore and/or assignment SubscriptingRef nodes to compute a
|
|
* new value for a container-type variable represented by the target. The
|
|
* expression references the target as the container source.
|
|
*/
|
|
static Query *
|
|
transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
|
|
{
|
|
Query *qry = makeNode(Query);
|
|
ColumnRef *cref = makeNode(ColumnRef);
|
|
List *indirection = stmt->indirection;
|
|
int nnames = stmt->nnames;
|
|
SelectStmt *sstmt = stmt->val;
|
|
Node *target;
|
|
Oid targettype;
|
|
int32 targettypmod;
|
|
Oid targetcollation;
|
|
List *tlist;
|
|
TargetEntry *tle;
|
|
Oid type_id;
|
|
Node *qual;
|
|
ListCell *l;
|
|
|
|
/*
|
|
* First, construct a ColumnRef for the target variable. If the target
|
|
* has more than one dotted name, we have to pull the extra names out of
|
|
* the indirection list.
|
|
*/
|
|
cref->fields = list_make1(makeString(stmt->name));
|
|
cref->location = stmt->location;
|
|
if (nnames > 1)
|
|
{
|
|
/* avoid munging the raw parsetree */
|
|
indirection = list_copy(indirection);
|
|
while (--nnames > 0 && indirection != NIL)
|
|
{
|
|
Node *ind = (Node *) linitial(indirection);
|
|
|
|
if (!IsA(ind, String))
|
|
elog(ERROR, "invalid name count in PLAssignStmt");
|
|
cref->fields = lappend(cref->fields, ind);
|
|
indirection = list_delete_first(indirection);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Transform the target reference. Typically we will get back a Param
|
|
* node, but there's no reason to be too picky about its type.
|
|
*/
|
|
target = transformExpr(pstate, (Node *) cref,
|
|
EXPR_KIND_UPDATE_TARGET);
|
|
targettype = exprType(target);
|
|
targettypmod = exprTypmod(target);
|
|
targetcollation = exprCollation(target);
|
|
|
|
/*
|
|
* The rest mostly matches transformSelectStmt, except that we needn't
|
|
* consider WITH or INTO, and we build a targetlist our own way.
|
|
*/
|
|
qry->commandType = CMD_SELECT;
|
|
pstate->p_is_insert = false;
|
|
|
|
/* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
|
|
pstate->p_locking_clause = sstmt->lockingClause;
|
|
|
|
/* make WINDOW info available for window functions, too */
|
|
pstate->p_windowdefs = sstmt->windowClause;
|
|
|
|
/* process the FROM clause */
|
|
transformFromClause(pstate, sstmt->fromClause);
|
|
|
|
/* initially transform the targetlist as if in SELECT */
|
|
tlist = transformTargetList(pstate, sstmt->targetList,
|
|
EXPR_KIND_SELECT_TARGET);
|
|
|
|
/* we should have exactly one targetlist item */
|
|
if (list_length(tlist) != 1)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg_plural("assignment source returned %d column",
|
|
"assignment source returned %d columns",
|
|
list_length(tlist),
|
|
list_length(tlist))));
|
|
|
|
tle = linitial_node(TargetEntry, tlist);
|
|
|
|
/*
|
|
* This next bit is similar to transformAssignedExpr; the key difference
|
|
* is we use COERCION_PLPGSQL not COERCION_ASSIGNMENT.
|
|
*/
|
|
type_id = exprType((Node *) tle->expr);
|
|
|
|
pstate->p_expr_kind = EXPR_KIND_UPDATE_TARGET;
|
|
|
|
if (indirection)
|
|
{
|
|
tle->expr = (Expr *)
|
|
transformAssignmentIndirection(pstate,
|
|
target,
|
|
stmt->name,
|
|
false,
|
|
targettype,
|
|
targettypmod,
|
|
targetcollation,
|
|
indirection,
|
|
list_head(indirection),
|
|
(Node *) tle->expr,
|
|
COERCION_PLPGSQL,
|
|
exprLocation(target));
|
|
}
|
|
else if (targettype != type_id &&
|
|
(targettype == RECORDOID || ISCOMPLEX(targettype)) &&
|
|
(type_id == RECORDOID || ISCOMPLEX(type_id)))
|
|
{
|
|
/*
|
|
* Hack: do not let coerce_to_target_type() deal with inconsistent
|
|
* composite types. Just pass the expression result through as-is,
|
|
* and let the PL/pgSQL executor do the conversion its way. This is
|
|
* rather bogus, but it's needed for backwards compatibility.
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* For normal non-qualified target column, do type checking and
|
|
* coercion.
|
|
*/
|
|
Node *orig_expr = (Node *) tle->expr;
|
|
|
|
tle->expr = (Expr *)
|
|
coerce_to_target_type(pstate,
|
|
orig_expr, type_id,
|
|
targettype, targettypmod,
|
|
COERCION_PLPGSQL,
|
|
COERCE_IMPLICIT_CAST,
|
|
-1);
|
|
/* With COERCION_PLPGSQL, this error is probably unreachable */
|
|
if (tle->expr == NULL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
errmsg("variable \"%s\" is of type %s"
|
|
" but expression is of type %s",
|
|
stmt->name,
|
|
format_type_be(targettype),
|
|
format_type_be(type_id)),
|
|
errhint("You will need to rewrite or cast the expression."),
|
|
parser_errposition(pstate, exprLocation(orig_expr))));
|
|
}
|
|
|
|
pstate->p_expr_kind = EXPR_KIND_NONE;
|
|
|
|
qry->targetList = list_make1(tle);
|
|
|
|
/* transform WHERE */
|
|
qual = transformWhereClause(pstate, sstmt->whereClause,
|
|
EXPR_KIND_WHERE, "WHERE");
|
|
|
|
/* initial processing of HAVING clause is much like WHERE clause */
|
|
qry->havingQual = transformWhereClause(pstate, sstmt->havingClause,
|
|
EXPR_KIND_HAVING, "HAVING");
|
|
|
|
/*
|
|
* 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,
|
|
sstmt->sortClause,
|
|
&qry->targetList,
|
|
EXPR_KIND_ORDER_BY,
|
|
false /* allow SQL92 rules */ );
|
|
|
|
qry->groupClause = transformGroupClause(pstate,
|
|
sstmt->groupClause,
|
|
&qry->groupingSets,
|
|
&qry->targetList,
|
|
qry->sortClause,
|
|
EXPR_KIND_GROUP_BY,
|
|
false /* allow SQL92 rules */ );
|
|
|
|
if (sstmt->distinctClause == NIL)
|
|
{
|
|
qry->distinctClause = NIL;
|
|
qry->hasDistinctOn = false;
|
|
}
|
|
else if (linitial(sstmt->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,
|
|
sstmt->distinctClause,
|
|
&qry->targetList,
|
|
qry->sortClause);
|
|
qry->hasDistinctOn = true;
|
|
}
|
|
|
|
/* transform LIMIT */
|
|
qry->limitOffset = transformLimitClause(pstate, sstmt->limitOffset,
|
|
EXPR_KIND_OFFSET, "OFFSET",
|
|
sstmt->limitOption);
|
|
qry->limitCount = transformLimitClause(pstate, sstmt->limitCount,
|
|
EXPR_KIND_LIMIT, "LIMIT",
|
|
sstmt->limitOption);
|
|
qry->limitOption = sstmt->limitOption;
|
|
|
|
/* transform window clauses after we have seen all window functions */
|
|
qry->windowClause = transformWindowDefinitions(pstate,
|
|
pstate->p_windowdefs,
|
|
&qry->targetList);
|
|
|
|
qry->rtable = pstate->p_rtable;
|
|
qry->rteperminfos = pstate->p_rteperminfos;
|
|
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
|
|
|
|
qry->hasSubLinks = pstate->p_hasSubLinks;
|
|
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
|
|
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
|
|
qry->hasAggs = pstate->p_hasAggs;
|
|
|
|
foreach(l, sstmt->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;
|
|
}
|
|
|
|
|
|
/*
|
|
* transformDeclareCursorStmt -
|
|
* transform a DECLARE CURSOR Statement
|
|
*
|
|
* DECLARE CURSOR 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 *
|
|
transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
|
|
{
|
|
Query *result;
|
|
Query *query;
|
|
|
|
if ((stmt->options & CURSOR_OPT_SCROLL) &&
|
|
(stmt->options & CURSOR_OPT_NO_SCROLL))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
|
|
/* translator: %s is a SQL keyword */
|
|
errmsg("cannot specify both %s and %s",
|
|
"SCROLL", "NO SCROLL")));
|
|
|
|
if ((stmt->options & CURSOR_OPT_ASENSITIVE) &&
|
|
(stmt->options & CURSOR_OPT_INSENSITIVE))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
|
|
/* translator: %s is a SQL keyword */
|
|
errmsg("cannot specify both %s and %s",
|
|
"ASENSITIVE", "INSENSITIVE")));
|
|
|
|
/* Transform contained query, not allowing SELECT INTO */
|
|
query = transformStmt(pstate, stmt->query);
|
|
stmt->query = (Node *) query;
|
|
|
|
/* Grammar should not have allowed anything but SELECT */
|
|
if (!IsA(query, Query) ||
|
|
query->commandType != CMD_SELECT)
|
|
elog(ERROR, "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 (query->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 (query->rowMarks != NIL && (stmt->options & CURSOR_OPT_HOLD))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("DECLARE CURSOR WITH HOLD ... %s is not supported",
|
|
LCS_asString(((RowMarkClause *)
|
|
linitial(query->rowMarks))->strength)),
|
|
errdetail("Holdable cursors must be READ ONLY.")));
|
|
|
|
/* FOR UPDATE and SCROLL are not compatible */
|
|
if (query->rowMarks != NIL && (stmt->options & CURSOR_OPT_SCROLL))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("DECLARE SCROLL CURSOR ... %s is not supported",
|
|
LCS_asString(((RowMarkClause *)
|
|
linitial(query->rowMarks))->strength)),
|
|
errdetail("Scrollable cursors must be READ ONLY.")));
|
|
|
|
/* FOR UPDATE and INSENSITIVE are not compatible */
|
|
if (query->rowMarks != NIL && (stmt->options & CURSOR_OPT_INSENSITIVE))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("DECLARE INSENSITIVE CURSOR ... %s is not valid",
|
|
LCS_asString(((RowMarkClause *)
|
|
linitial(query->rowMarks))->strength)),
|
|
errdetail("Insensitive cursors must be READ ONLY.")));
|
|
|
|
/* represent the command as a utility Query */
|
|
result = makeNode(Query);
|
|
result->commandType = CMD_UTILITY;
|
|
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;
|
|
bool generic_plan = false;
|
|
Oid *paramTypes = NULL;
|
|
int numParams = 0;
|
|
|
|
/*
|
|
* If we have no external source of parameter definitions, and the
|
|
* GENERIC_PLAN option is specified, then accept variable parameter
|
|
* definitions (similarly to PREPARE, for example).
|
|
*/
|
|
if (pstate->p_paramref_hook == NULL)
|
|
{
|
|
ListCell *lc;
|
|
|
|
foreach(lc, stmt->options)
|
|
{
|
|
DefElem *opt = (DefElem *) lfirst(lc);
|
|
|
|
if (strcmp(opt->defname, "generic_plan") == 0)
|
|
generic_plan = defGetBoolean(opt);
|
|
/* don't "break", as we want the last value */
|
|
}
|
|
if (generic_plan)
|
|
setup_parse_variable_parameters(pstate, ¶mTypes, &numParams);
|
|
}
|
|
|
|
/* transform contained query, allowing SELECT INTO */
|
|
stmt->query = (Node *) transformOptionalSelectInto(pstate, stmt->query);
|
|
|
|
/* make sure all is well with parameter types */
|
|
if (generic_plan)
|
|
check_variable_parameters(pstate, (Query *) 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 DECLARE CURSOR and EXPLAIN, transform the contained statement now.
|
|
*/
|
|
static Query *
|
|
transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
|
|
{
|
|
Query *result;
|
|
Query *query;
|
|
|
|
/* transform contained query, not allowing SELECT INTO */
|
|
query = transformStmt(pstate, stmt->query);
|
|
stmt->query = (Node *) query;
|
|
|
|
/* additional work needed for CREATE MATERIALIZED VIEW */
|
|
if (stmt->objtype == OBJECT_MATVIEW)
|
|
{
|
|
/*
|
|
* Prohibit a data-modifying CTE in the query used to create a
|
|
* materialized view. It's not sufficiently clear what the user would
|
|
* want to happen if the MV is refreshed or incrementally maintained.
|
|
*/
|
|
if (query->hasModifyingCTE)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("materialized views must not use data-modifying statements in WITH")));
|
|
|
|
/*
|
|
* Check whether any temporary database objects are used in the
|
|
* creation query. It would be hard to refresh data or incrementally
|
|
* maintain it if a source disappeared.
|
|
*/
|
|
if (isQueryUsingTempRelation(query))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("materialized views must not use temporary tables or views")));
|
|
|
|
/*
|
|
* A materialized view would either need to save parameters for use in
|
|
* maintaining/loading the data or prohibit them entirely. The latter
|
|
* seems safer and more sane.
|
|
*/
|
|
if (query_contains_extern_params(query))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("materialized views may not be defined using bound parameters")));
|
|
|
|
/*
|
|
* For now, we disallow unlogged materialized views, because it seems
|
|
* like a bad idea for them to just go to empty after a crash. (If we
|
|
* could mark them as unpopulated, that would be better, but that
|
|
* requires catalog changes which crash recovery can't presently
|
|
* handle.)
|
|
*/
|
|
if (stmt->into->rel->relpersistence == RELPERSISTENCE_UNLOGGED)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("materialized views cannot be unlogged")));
|
|
|
|
/*
|
|
* At runtime, we'll need a copy of the parsed-but-not-rewritten Query
|
|
* for purposes of creating the view's ON SELECT rule. We stash that
|
|
* in the IntoClause because that's where intorel_startup() can
|
|
* conveniently get it from.
|
|
*/
|
|
stmt->into->viewQuery = (Node *) copyObject(query);
|
|
}
|
|
|
|
/* represent the command as a utility Query */
|
|
result = makeNode(Query);
|
|
result->commandType = CMD_UTILITY;
|
|
result->utilityStmt = (Node *) stmt;
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* transform a CallStmt
|
|
*/
|
|
static Query *
|
|
transformCallStmt(ParseState *pstate, CallStmt *stmt)
|
|
{
|
|
List *targs;
|
|
ListCell *lc;
|
|
Node *node;
|
|
FuncExpr *fexpr;
|
|
HeapTuple proctup;
|
|
Datum proargmodes;
|
|
bool isNull;
|
|
List *outargs = NIL;
|
|
Query *result;
|
|
|
|
/*
|
|
* First, do standard parse analysis on the procedure call and its
|
|
* arguments, allowing us to identify the called procedure.
|
|
*/
|
|
targs = NIL;
|
|
foreach(lc, stmt->funccall->args)
|
|
{
|
|
targs = lappend(targs, transformExpr(pstate,
|
|
(Node *) lfirst(lc),
|
|
EXPR_KIND_CALL_ARGUMENT));
|
|
}
|
|
|
|
node = ParseFuncOrColumn(pstate,
|
|
stmt->funccall->funcname,
|
|
targs,
|
|
pstate->p_last_srf,
|
|
stmt->funccall,
|
|
true,
|
|
stmt->funccall->location);
|
|
|
|
assign_expr_collations(pstate, node);
|
|
|
|
fexpr = castNode(FuncExpr, node);
|
|
|
|
proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fexpr->funcid));
|
|
if (!HeapTupleIsValid(proctup))
|
|
elog(ERROR, "cache lookup failed for function %u", fexpr->funcid);
|
|
|
|
/*
|
|
* Expand the argument list to deal with named-argument notation and
|
|
* default arguments. For ordinary FuncExprs this'd be done during
|
|
* planning, but a CallStmt doesn't go through planning, and there seems
|
|
* no good reason not to do it here.
|
|
*/
|
|
fexpr->args = expand_function_arguments(fexpr->args,
|
|
true,
|
|
fexpr->funcresulttype,
|
|
proctup);
|
|
|
|
/* Fetch proargmodes; if it's null, there are no output args */
|
|
proargmodes = SysCacheGetAttr(PROCOID, proctup,
|
|
Anum_pg_proc_proargmodes,
|
|
&isNull);
|
|
if (!isNull)
|
|
{
|
|
/*
|
|
* Split the list into input arguments in fexpr->args and output
|
|
* arguments in stmt->outargs. INOUT arguments appear in both lists.
|
|
*/
|
|
ArrayType *arr;
|
|
int numargs;
|
|
char *argmodes;
|
|
List *inargs;
|
|
int i;
|
|
|
|
arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */
|
|
numargs = list_length(fexpr->args);
|
|
if (ARR_NDIM(arr) != 1 ||
|
|
ARR_DIMS(arr)[0] != numargs ||
|
|
ARR_HASNULL(arr) ||
|
|
ARR_ELEMTYPE(arr) != CHAROID)
|
|
elog(ERROR, "proargmodes is not a 1-D char array of length %d or it contains nulls",
|
|
numargs);
|
|
argmodes = (char *) ARR_DATA_PTR(arr);
|
|
|
|
inargs = NIL;
|
|
i = 0;
|
|
foreach(lc, fexpr->args)
|
|
{
|
|
Node *n = lfirst(lc);
|
|
|
|
switch (argmodes[i])
|
|
{
|
|
case PROARGMODE_IN:
|
|
case PROARGMODE_VARIADIC:
|
|
inargs = lappend(inargs, n);
|
|
break;
|
|
case PROARGMODE_OUT:
|
|
outargs = lappend(outargs, n);
|
|
break;
|
|
case PROARGMODE_INOUT:
|
|
inargs = lappend(inargs, n);
|
|
outargs = lappend(outargs, copyObject(n));
|
|
break;
|
|
default:
|
|
/* note we don't support PROARGMODE_TABLE */
|
|
elog(ERROR, "invalid argmode %c for procedure",
|
|
argmodes[i]);
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
fexpr->args = inargs;
|
|
}
|
|
|
|
stmt->funcexpr = fexpr;
|
|
stmt->outargs = outargs;
|
|
|
|
ReleaseSysCache(proctup);
|
|
|
|
/* represent the command as a utility Query */
|
|
result = makeNode(Query);
|
|
result->commandType = CMD_UTILITY;
|
|
result->utilityStmt = (Node *) stmt;
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Produce a string representation of a LockClauseStrength value.
|
|
* This should only be applied to valid values (not LCS_NONE).
|
|
*/
|
|
const char *
|
|
LCS_asString(LockClauseStrength strength)
|
|
{
|
|
switch (strength)
|
|
{
|
|
case LCS_NONE:
|
|
Assert(false);
|
|
break;
|
|
case LCS_FORKEYSHARE:
|
|
return "FOR KEY SHARE";
|
|
case LCS_FORSHARE:
|
|
return "FOR SHARE";
|
|
case LCS_FORNOKEYUPDATE:
|
|
return "FOR NO KEY UPDATE";
|
|
case LCS_FORUPDATE:
|
|
return "FOR UPDATE";
|
|
}
|
|
return "FOR some"; /* shouldn't happen */
|
|
}
|
|
|
|
/*
|
|
* Check for features that are not supported with FOR [KEY] UPDATE/SHARE.
|
|
*
|
|
* exported so planner can check again after rewriting, query pullup, etc
|
|
*/
|
|
void
|
|
CheckSelectLocking(Query *qry, LockClauseStrength strength)
|
|
{
|
|
Assert(strength != LCS_NONE); /* else caller error */
|
|
|
|
if (qry->setOperations)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("%s is not allowed with UNION/INTERSECT/EXCEPT",
|
|
LCS_asString(strength))));
|
|
if (qry->distinctClause != NIL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("%s is not allowed with DISTINCT clause",
|
|
LCS_asString(strength))));
|
|
if (qry->groupClause != NIL || qry->groupingSets != NIL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("%s is not allowed with GROUP BY clause",
|
|
LCS_asString(strength))));
|
|
if (qry->havingQual != NULL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("%s is not allowed with HAVING clause",
|
|
LCS_asString(strength))));
|
|
if (qry->hasAggs)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("%s is not allowed with aggregate functions",
|
|
LCS_asString(strength))));
|
|
if (qry->hasWindowFuncs)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("%s is not allowed with window functions",
|
|
LCS_asString(strength))));
|
|
if (qry->hasTargetSRFs)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("%s is not allowed with set-returning functions in the target list",
|
|
LCS_asString(strength))));
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
ListCell *rt;
|
|
Index i;
|
|
LockingClause *allrels;
|
|
|
|
CheckSelectLocking(qry, lc->strength);
|
|
|
|
/* make a clause we can pass down to subqueries to select all rels */
|
|
allrels = makeNode(LockingClause);
|
|
allrels->lockedRels = NIL; /* indicates all rels */
|
|
allrels->strength = lc->strength;
|
|
allrels->waitPolicy = lc->waitPolicy;
|
|
|
|
if (lockedRels == NIL)
|
|
{
|
|
/*
|
|
* Lock all regular tables used in query and its subqueries. We
|
|
* examine inFromCl to exclude auto-added RTEs, particularly NEW/OLD
|
|
* in rules. This is a bit of an abuse of a mostly-obsolete flag, but
|
|
* it's convenient. We can't rely on the namespace mechanism that has
|
|
* largely replaced inFromCl, since for example we need to lock
|
|
* base-relation RTEs even if they are masked by upper joins.
|
|
*/
|
|
i = 0;
|
|
foreach(rt, qry->rtable)
|
|
{
|
|
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
|
|
|
|
++i;
|
|
if (!rte->inFromCl)
|
|
continue;
|
|
switch (rte->rtekind)
|
|
{
|
|
case RTE_RELATION:
|
|
{
|
|
RTEPermissionInfo *perminfo;
|
|
|
|
applyLockingClause(qry, i,
|
|
lc->strength,
|
|
lc->waitPolicy,
|
|
pushedDown);
|
|
perminfo = getRTEPermissionInfo(qry->rteperminfos, rte);
|
|
perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
|
|
}
|
|
break;
|
|
case RTE_SUBQUERY:
|
|
applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
|
|
pushedDown);
|
|
|
|
/*
|
|
* FOR 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;
|
|
default:
|
|
/* ignore JOIN, SPECIAL, FUNCTION, VALUES, CTE RTEs */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Lock just the named tables. As above, we allow locking any base
|
|
* relation regardless of alias-visibility rules, so we need to
|
|
* examine inFromCl to exclude OLD/NEW.
|
|
*/
|
|
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),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("%s must specify unqualified relation names",
|
|
LCS_asString(lc->strength)),
|
|
parser_errposition(pstate, thisrel->location)));
|
|
|
|
i = 0;
|
|
foreach(rt, qry->rtable)
|
|
{
|
|
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
|
|
char *rtename = rte->eref->aliasname;
|
|
|
|
++i;
|
|
if (!rte->inFromCl)
|
|
continue;
|
|
|
|
/*
|
|
* A join RTE without an alias is not visible as a relation
|
|
* name and needs to be skipped (otherwise it might hide a
|
|
* base relation with the same name), except if it has a USING
|
|
* alias, which *is* visible.
|
|
*
|
|
* Subquery and values RTEs without aliases are never visible
|
|
* as relation names and must always be skipped.
|
|
*/
|
|
if (rte->alias == NULL)
|
|
{
|
|
if (rte->rtekind == RTE_JOIN)
|
|
{
|
|
if (rte->join_using_alias == NULL)
|
|
continue;
|
|
rtename = rte->join_using_alias->aliasname;
|
|
}
|
|
else if (rte->rtekind == RTE_SUBQUERY ||
|
|
rte->rtekind == RTE_VALUES)
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(rtename, thisrel->relname) == 0)
|
|
{
|
|
switch (rte->rtekind)
|
|
{
|
|
case RTE_RELATION:
|
|
{
|
|
RTEPermissionInfo *perminfo;
|
|
|
|
applyLockingClause(qry, i,
|
|
lc->strength,
|
|
lc->waitPolicy,
|
|
pushedDown);
|
|
perminfo = getRTEPermissionInfo(qry->rteperminfos, rte);
|
|
perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
|
|
}
|
|
break;
|
|
case RTE_SUBQUERY:
|
|
applyLockingClause(qry, i, lc->strength,
|
|
lc->waitPolicy, pushedDown);
|
|
/* see comment above */
|
|
transformLockingClause(pstate, rte->subquery,
|
|
allrels, true);
|
|
break;
|
|
case RTE_JOIN:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("%s cannot be applied to a join",
|
|
LCS_asString(lc->strength)),
|
|
parser_errposition(pstate, thisrel->location)));
|
|
break;
|
|
case RTE_FUNCTION:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("%s cannot be applied to a function",
|
|
LCS_asString(lc->strength)),
|
|
parser_errposition(pstate, thisrel->location)));
|
|
break;
|
|
case RTE_TABLEFUNC:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("%s cannot be applied to a table function",
|
|
LCS_asString(lc->strength)),
|
|
parser_errposition(pstate, thisrel->location)));
|
|
break;
|
|
case RTE_VALUES:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("%s cannot be applied to VALUES",
|
|
LCS_asString(lc->strength)),
|
|
parser_errposition(pstate, thisrel->location)));
|
|
break;
|
|
case RTE_CTE:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("%s cannot be applied to a WITH query",
|
|
LCS_asString(lc->strength)),
|
|
parser_errposition(pstate, thisrel->location)));
|
|
break;
|
|
case RTE_NAMEDTUPLESTORE:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("%s cannot be applied to a named tuplestore",
|
|
LCS_asString(lc->strength)),
|
|
parser_errposition(pstate, thisrel->location)));
|
|
break;
|
|
|
|
/* Shouldn't be possible to see RTE_RESULT here */
|
|
|
|
default:
|
|
elog(ERROR, "unrecognized RTE type: %d",
|
|
(int) rte->rtekind);
|
|
break;
|
|
}
|
|
break; /* out of foreach loop */
|
|
}
|
|
}
|
|
if (rt == NULL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_TABLE),
|
|
/*------
|
|
translator: %s is a SQL row locking clause such as FOR UPDATE */
|
|
errmsg("relation \"%s\" in %s clause not found in FROM clause",
|
|
thisrel->relname,
|
|
LCS_asString(lc->strength)),
|
|
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)
|
|
{
|
|
RowMarkClause *rc;
|
|
|
|
Assert(strength != LCS_NONE); /* else caller error */
|
|
|
|
/* 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 with more than one locking strength,
|
|
* use 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->waitPolicy = Max(rc->waitPolicy, waitPolicy);
|
|
rc->pushedDown &= pushedDown;
|
|
return;
|
|
}
|
|
|
|
/* Make a new RowMarkClause */
|
|
rc = makeNode(RowMarkClause);
|
|
rc->rti = rtindex;
|
|
rc->strength = strength;
|
|
rc->waitPolicy = waitPolicy;
|
|
rc->pushedDown = pushedDown;
|
|
qry->rowMarks = lappend(qry->rowMarks, rc);
|
|
}
|
|
|
|
/*
|
|
* Coverage testing for raw_expression_tree_walker().
|
|
*
|
|
* When enabled, we run raw_expression_tree_walker() over every DML statement
|
|
* submitted to parse analysis. Without this provision, that function is only
|
|
* applied in limited cases involving CTEs, and we don't really want to have
|
|
* to test everything inside as well as outside a CTE.
|
|
*/
|
|
#ifdef RAW_EXPRESSION_COVERAGE_TEST
|
|
|
|
static bool
|
|
test_raw_expression_coverage(Node *node, void *context)
|
|
{
|
|
if (node == NULL)
|
|
return false;
|
|
return raw_expression_tree_walker(node,
|
|
test_raw_expression_coverage,
|
|
context);
|
|
}
|
|
|
|
#endif /* RAW_EXPRESSION_COVERAGE_TEST */
|