1911 lines
55 KiB
C
1911 lines
55 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 and EXPLAIN are exceptions because they contain
|
|
* optimizable statements.
|
|
*
|
|
*
|
|
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.370 2007/11/15 21:14:36 momjian Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "catalog/pg_type.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "optimizer/clauses.h"
|
|
#include "optimizer/var.h"
|
|
#include "parser/analyze.h"
|
|
#include "parser/parse_agg.h"
|
|
#include "parser/parse_clause.h"
|
|
#include "parser/parse_coerce.h"
|
|
#include "parser/parse_expr.h"
|
|
#include "parser/parse_relation.h"
|
|
#include "parser/parse_target.h"
|
|
#include "parser/parsetree.h"
|
|
|
|
|
|
typedef struct
|
|
{
|
|
Oid *paramTypes;
|
|
int numParams;
|
|
} check_parameter_resolution_context;
|
|
|
|
|
|
static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
|
|
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
|
|
static List *transformInsertRow(ParseState *pstate, List *exprlist,
|
|
List *stmtcols, List *icolumns, List *attrnos);
|
|
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);
|
|
static void getSetColTypes(ParseState *pstate, Node *node,
|
|
List **colTypes, List **colTypmods);
|
|
static void applyColumnNames(List *dst, List *src);
|
|
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
|
|
static List *transformReturningList(ParseState *pstate, List *returningList);
|
|
static Query *transformDeclareCursorStmt(ParseState *pstate,
|
|
DeclareCursorStmt *stmt);
|
|
static Query *transformExplainStmt(ParseState *pstate,
|
|
ExplainStmt *stmt);
|
|
static void transformLockingClause(Query *qry, LockingClause *lc);
|
|
static bool check_parameter_resolution_walker(Node *node,
|
|
check_parameter_resolution_context *context);
|
|
|
|
|
|
/*
|
|
* parse_analyze
|
|
* Analyze a raw parse tree and transform it to Query form.
|
|
*
|
|
* If available, pass the source text from which the raw parse tree was
|
|
* generated; it's OK to pass NULL if this is not available.
|
|
*
|
|
* Optionally, information about $n parameter types can be supplied.
|
|
* References to $n indexes not defined by paramTypes[] are disallowed.
|
|
*
|
|
* The result is a Query node. Optimizable statements require considerable
|
|
* transformation, while utility-type statements are simply hung off
|
|
* a dummy CMD_UTILITY Query node.
|
|
*/
|
|
Query *
|
|
parse_analyze(Node *parseTree, const char *sourceText,
|
|
Oid *paramTypes, int numParams)
|
|
{
|
|
ParseState *pstate = make_parsestate(NULL);
|
|
Query *query;
|
|
|
|
pstate->p_sourcetext = sourceText;
|
|
pstate->p_paramtypes = paramTypes;
|
|
pstate->p_numparams = numParams;
|
|
pstate->p_variableparams = false;
|
|
|
|
query = transformStmt(pstate, parseTree);
|
|
|
|
free_parsestate(pstate);
|
|
|
|
return query;
|
|
}
|
|
|
|
/*
|
|
* parse_analyze_varparams
|
|
*
|
|
* This variant is used when it's okay to deduce information about $n
|
|
* symbol datatypes from context. The passed-in paramTypes[] array can
|
|
* be modified or enlarged (via repalloc).
|
|
*/
|
|
Query *
|
|
parse_analyze_varparams(Node *parseTree, const char *sourceText,
|
|
Oid **paramTypes, int *numParams)
|
|
{
|
|
ParseState *pstate = make_parsestate(NULL);
|
|
Query *query;
|
|
|
|
pstate->p_sourcetext = sourceText;
|
|
pstate->p_paramtypes = *paramTypes;
|
|
pstate->p_numparams = *numParams;
|
|
pstate->p_variableparams = true;
|
|
|
|
query = transformStmt(pstate, parseTree);
|
|
|
|
*paramTypes = pstate->p_paramtypes;
|
|
*numParams = pstate->p_numparams;
|
|
|
|
free_parsestate(pstate);
|
|
|
|
/* make sure all is well with parameter types */
|
|
if (*numParams > 0)
|
|
{
|
|
check_parameter_resolution_context context;
|
|
|
|
context.paramTypes = *paramTypes;
|
|
context.numParams = *numParams;
|
|
check_parameter_resolution_walker((Node *) query, &context);
|
|
}
|
|
|
|
return query;
|
|
}
|
|
|
|
/*
|
|
* parse_sub_analyze
|
|
* Entry point for recursively analyzing a sub-statement.
|
|
*/
|
|
Query *
|
|
parse_sub_analyze(Node *parseTree, ParseState *parentParseState)
|
|
{
|
|
ParseState *pstate = make_parsestate(parentParseState);
|
|
Query *query;
|
|
|
|
query = transformStmt(pstate, parseTree);
|
|
|
|
free_parsestate(pstate);
|
|
|
|
return query;
|
|
}
|
|
|
|
/*
|
|
* transformStmt -
|
|
* transform a Parse tree into a Query tree.
|
|
*/
|
|
Query *
|
|
transformStmt(ParseState *pstate, Node *parseTree)
|
|
{
|
|
Query *result;
|
|
|
|
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_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;
|
|
|
|
/*
|
|
* Special cases
|
|
*/
|
|
case T_DeclareCursorStmt:
|
|
result = transformDeclareCursorStmt(pstate,
|
|
(DeclareCursorStmt *) parseTree);
|
|
break;
|
|
|
|
case T_ExplainStmt:
|
|
result = transformExplainStmt(pstate,
|
|
(ExplainStmt *) 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;
|
|
}
|
|
|
|
/*
|
|
* transformDeleteStmt -
|
|
* transforms a Delete Statement
|
|
*/
|
|
static Query *
|
|
transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
|
|
{
|
|
Query *qry = makeNode(Query);
|
|
Node *qual;
|
|
|
|
qry->commandType = CMD_DELETE;
|
|
|
|
/* set up range table with just the result rel */
|
|
qry->resultRelation = setTargetTable(pstate, stmt->relation,
|
|
interpretInhOption(stmt->relation->inhOpt),
|
|
true,
|
|
ACL_DELETE);
|
|
|
|
qry->distinctClause = NIL;
|
|
|
|
/*
|
|
* The USING clause is non-standard SQL syntax, and is equivalent in
|
|
* functionality to the FROM list that can be specified for UPDATE. The
|
|
* USING keyword is used rather than FROM because FROM is already a
|
|
* keyword in the DELETE syntax.
|
|
*/
|
|
transformFromClause(pstate, stmt->usingClause);
|
|
|
|
qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
|
|
|
|
qry->returningList = transformReturningList(pstate, stmt->returningList);
|
|
|
|
/* done building the range table and jointree */
|
|
qry->rtable = pstate->p_rtable;
|
|
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
|
|
|
|
qry->hasSubLinks = pstate->p_hasSubLinks;
|
|
qry->hasAggs = pstate->p_hasAggs;
|
|
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_relnamespace;
|
|
List *sub_varnamespace;
|
|
List *icolumns;
|
|
List *attrnos;
|
|
RangeTblEntry *rte;
|
|
RangeTblRef *rtr;
|
|
ListCell *icols;
|
|
ListCell *attnos;
|
|
ListCell *lc;
|
|
|
|
qry->commandType = CMD_INSERT;
|
|
pstate->p_is_insert = true;
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
isGeneralSelect = (selectStmt && selectStmt->valuesLists == NIL);
|
|
|
|
/*
|
|
* If a non-nil rangetable/namespace was passed in, and we are doing
|
|
* INSERT/SELECT, arrange to pass the rangetable/namespace down to the
|
|
* SELECT. This can only happen if we are inside a CREATE RULE, and in
|
|
* that case we want the rule's OLD and NEW rtable entries to appear as
|
|
* part of the SELECT's rtable, not as outer references for it. (Kluge!)
|
|
* The SELECT's joinlist is not affected however. We must do this before
|
|
* adding the target table to the INSERT's rtable.
|
|
*/
|
|
if (isGeneralSelect)
|
|
{
|
|
sub_rtable = pstate->p_rtable;
|
|
pstate->p_rtable = NIL;
|
|
sub_relnamespace = pstate->p_relnamespace;
|
|
pstate->p_relnamespace = NIL;
|
|
sub_varnamespace = pstate->p_varnamespace;
|
|
pstate->p_varnamespace = NIL;
|
|
}
|
|
else
|
|
{
|
|
sub_rtable = NIL; /* not used, but keep compiler quiet */
|
|
sub_relnamespace = NIL;
|
|
sub_varnamespace = NIL;
|
|
}
|
|
|
|
/*
|
|
* Must get write lock on INSERT target table before scanning SELECT, else
|
|
* we will grab the wrong kind of initial lock if the target table is also
|
|
* mentioned in the SELECT part. Note that the target table is not added
|
|
* to the joinlist or namespace.
|
|
*/
|
|
qry->resultRelation = setTargetTable(pstate, stmt->relation,
|
|
false, false, ACL_INSERT);
|
|
|
|
/* 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...)
|
|
*/
|
|
sub_pstate->p_rtable = sub_rtable;
|
|
sub_pstate->p_relnamespace = sub_relnamespace;
|
|
sub_pstate->p_varnamespace = sub_varnamespace;
|
|
|
|
selectQuery = transformStmt(sub_pstate, stmt->selectStmt);
|
|
|
|
free_parsestate(sub_pstate);
|
|
|
|
/* The grammar should have produced a SELECT, but it might have INTO */
|
|
Assert(IsA(selectQuery, Query));
|
|
Assert(selectQuery->commandType == CMD_SELECT);
|
|
Assert(selectQuery->utilityStmt == NULL);
|
|
if (selectQuery->intoClause)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("INSERT ... SELECT cannot specify INTO")));
|
|
|
|
/*
|
|
* Make the source be a subquery in the INSERT's rangetable, and add
|
|
* it to the INSERT's joinlist.
|
|
*/
|
|
rte = addRangeTableEntryForSubquery(pstate,
|
|
selectQuery,
|
|
makeAlias("*SELECT*", NIL),
|
|
false);
|
|
rtr = makeNode(RangeTblRef);
|
|
/* assume new rte is at end */
|
|
rtr->rtindex = list_length(pstate->p_rtable);
|
|
Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
|
|
pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
|
|
|
|
/*----------
|
|
* Generate an expression list for the INSERT that selects all the
|
|
* non-resjunk columns from the subquery. (INSERT's tlist must be
|
|
* separate from the subquery's tlist because we may add columns,
|
|
* insert datatype coercions, etc.)
|
|
*
|
|
* HACK: unknown-type constants and params in the SELECT's targetlist
|
|
* are copied up as-is rather than being referenced as subquery
|
|
* outputs. This is to ensure that when we try to coerce them to
|
|
* the target column's datatype, the right things happen (see
|
|
* special cases in coerce_type). Otherwise, this fails:
|
|
* INSERT INTO foo SELECT 'bar', ... FROM baz
|
|
*----------
|
|
*/
|
|
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
|
|
expr = (Expr *) makeVar(rtr->rtindex,
|
|
tle->resno,
|
|
exprType((Node *) tle->expr),
|
|
exprTypmod((Node *) tle->expr),
|
|
0);
|
|
exprList = lappend(exprList, expr);
|
|
}
|
|
|
|
/* Prepare row for assignment to target table */
|
|
exprList = transformInsertRow(pstate, exprList,
|
|
stmt->cols,
|
|
icolumns, attrnos);
|
|
}
|
|
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;
|
|
int sublist_length = -1;
|
|
|
|
foreach(lc, selectStmt->valuesLists)
|
|
{
|
|
List *sublist = (List *) lfirst(lc);
|
|
|
|
/* Do basic expression transformation (same as a ROW() expr) */
|
|
sublist = transformExpressionList(pstate, sublist);
|
|
|
|
/*
|
|
* 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")));
|
|
}
|
|
|
|
/* Prepare row for assignment to target table */
|
|
sublist = transformInsertRow(pstate, sublist,
|
|
stmt->cols,
|
|
icolumns, attrnos);
|
|
|
|
exprsLists = lappend(exprsLists, sublist);
|
|
}
|
|
|
|
/*
|
|
* There mustn't have been any table references in the expressions,
|
|
* else strange things would happen, like Cartesian products of those
|
|
* tables with the VALUES list ...
|
|
*/
|
|
if (pstate->p_joinlist != NIL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("VALUES must not contain table references")));
|
|
|
|
/*
|
|
* Another thing we can't currently support is NEW/OLD references in
|
|
* rules --- seems we'd need something like SQL99's LATERAL construct
|
|
* to ensure that the values would be available while evaluating the
|
|
* VALUES RTE. This is a shame. FIXME
|
|
*/
|
|
if (list_length(pstate->p_rtable) != 1 &&
|
|
contain_vars_of_level((Node *) exprsLists, 0))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("VALUES must not contain OLD or NEW references"),
|
|
errhint("Use SELECT ... UNION ALL ... instead.")));
|
|
|
|
/*
|
|
* Generate the VALUES RTE
|
|
*/
|
|
rte = addRangeTableEntryForValues(pstate, exprsLists, NULL, true);
|
|
rtr = makeNode(RangeTblRef);
|
|
/* assume new rte is at end */
|
|
rtr->rtindex = list_length(pstate->p_rtable);
|
|
Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
|
|
pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
|
|
|
|
/*
|
|
* Generate list of Vars referencing the RTE
|
|
*/
|
|
expandRTE(rte, rtr->rtindex, 0, false, NULL, &exprList);
|
|
}
|
|
else
|
|
{
|
|
/*----------
|
|
* Process INSERT ... VALUES with a single VALUES sublist.
|
|
* We treat this separately for efficiency and for historical
|
|
* compatibility --- specifically, allowing table references,
|
|
* such as
|
|
* INSERT INTO foo VALUES(bar.*)
|
|
*
|
|
* The sublist is just computed directly as the Query's targetlist,
|
|
* with no VALUES RTE. So it works just like SELECT without FROM.
|
|
*----------
|
|
*/
|
|
List *valuesLists = selectStmt->valuesLists;
|
|
|
|
Assert(list_length(valuesLists) == 1);
|
|
|
|
/* Do basic expression transformation (same as a ROW() expr) */
|
|
exprList = transformExpressionList(pstate,
|
|
(List *) linitial(valuesLists));
|
|
|
|
/* Prepare row for assignment to target table */
|
|
exprList = transformInsertRow(pstate, exprList,
|
|
stmt->cols,
|
|
icolumns, attrnos);
|
|
}
|
|
|
|
/*
|
|
* Generate query's target list using the computed list of expressions.
|
|
*/
|
|
qry->targetList = NIL;
|
|
icols = list_head(icolumns);
|
|
attnos = list_head(attrnos);
|
|
foreach(lc, exprList)
|
|
{
|
|
Expr *expr = (Expr *) lfirst(lc);
|
|
ResTarget *col;
|
|
TargetEntry *tle;
|
|
|
|
col = (ResTarget *) lfirst(icols);
|
|
Assert(IsA(col, ResTarget));
|
|
|
|
tle = makeTargetEntry(expr,
|
|
(AttrNumber) lfirst_int(attnos),
|
|
col->name,
|
|
false);
|
|
qry->targetList = lappend(qry->targetList, tle);
|
|
|
|
icols = lnext(icols);
|
|
attnos = lnext(attnos);
|
|
}
|
|
|
|
/*
|
|
* If we have a RETURNING clause, we need to add the target relation to
|
|
* the query namespace before processing it, so that Var references in
|
|
* RETURNING will work. Also, remove any namespace entries added in a
|
|
* sub-SELECT or VALUES list.
|
|
*/
|
|
if (stmt->returningList)
|
|
{
|
|
pstate->p_relnamespace = NIL;
|
|
pstate->p_varnamespace = NIL;
|
|
addRTEtoQuery(pstate, pstate->p_target_rangetblentry,
|
|
false, true, true);
|
|
qry->returningList = transformReturningList(pstate,
|
|
stmt->returningList);
|
|
}
|
|
|
|
/* done building the range table and jointree */
|
|
qry->rtable = pstate->p_rtable;
|
|
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
|
|
|
|
qry->hasSubLinks = pstate->p_hasSubLinks;
|
|
/* aggregates not allowed (but subselects are okay) */
|
|
if (pstate->p_hasAggs)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_GROUPING_ERROR),
|
|
errmsg("cannot use aggregate function in VALUES")));
|
|
|
|
return qry;
|
|
}
|
|
|
|
/*
|
|
* Prepare an INSERT row for assignment to the target table.
|
|
*
|
|
* The row might be either a VALUES row, or variables referencing a
|
|
* sub-SELECT output.
|
|
*/
|
|
static List *
|
|
transformInsertRow(ParseState *pstate, List *exprlist,
|
|
List *stmtcols, List *icolumns, List *attrnos)
|
|
{
|
|
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")));
|
|
if (stmtcols != NIL &&
|
|
list_length(exprlist) < list_length(icolumns))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("INSERT has more target columns than expressions")));
|
|
|
|
/*
|
|
* Prepare columns for assignment to target table.
|
|
*/
|
|
result = NIL;
|
|
icols = list_head(icolumns);
|
|
attnos = list_head(attrnos);
|
|
foreach(lc, exprlist)
|
|
{
|
|
Expr *expr = (Expr *) lfirst(lc);
|
|
ResTarget *col;
|
|
|
|
col = (ResTarget *) lfirst(icols);
|
|
Assert(IsA(col, ResTarget));
|
|
|
|
expr = transformAssignedExpr(pstate, expr,
|
|
col->name,
|
|
lfirst_int(attnos),
|
|
col->indirection,
|
|
col->location);
|
|
|
|
result = lappend(result, expr);
|
|
|
|
icols = lnext(icols);
|
|
attnos = lnext(attnos);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
|
|
pstate->p_locking_clause = stmt->lockingClause;
|
|
|
|
/* process the FROM clause */
|
|
transformFromClause(pstate, stmt->fromClause);
|
|
|
|
/* transform targetlist */
|
|
qry->targetList = transformTargetList(pstate, stmt->targetList);
|
|
|
|
/* mark column origins */
|
|
markTargetListOrigins(pstate, qry->targetList);
|
|
|
|
/* transform WHERE */
|
|
qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
|
|
|
|
/*
|
|
* Initial processing of HAVING clause is just like WHERE clause.
|
|
*/
|
|
qry->havingQual = transformWhereClause(pstate, stmt->havingClause,
|
|
"HAVING");
|
|
|
|
/*
|
|
* Transform sorting/grouping stuff. Do ORDER BY first because both
|
|
* transformGroupClause and transformDistinctClause need the results.
|
|
*/
|
|
qry->sortClause = transformSortClause(pstate,
|
|
stmt->sortClause,
|
|
&qry->targetList,
|
|
true /* fix unknowns */ );
|
|
|
|
qry->groupClause = transformGroupClause(pstate,
|
|
stmt->groupClause,
|
|
&qry->targetList,
|
|
qry->sortClause);
|
|
|
|
qry->distinctClause = transformDistinctClause(pstate,
|
|
stmt->distinctClause,
|
|
&qry->targetList,
|
|
&qry->sortClause);
|
|
|
|
qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
|
|
"OFFSET");
|
|
qry->limitCount = transformLimitClause(pstate, stmt->limitCount,
|
|
"LIMIT");
|
|
|
|
/* handle any SELECT INTO/CREATE TABLE AS spec */
|
|
if (stmt->intoClause)
|
|
{
|
|
qry->intoClause = stmt->intoClause;
|
|
if (stmt->intoClause->colNames)
|
|
applyColumnNames(qry->targetList, stmt->intoClause->colNames);
|
|
}
|
|
|
|
qry->rtable = pstate->p_rtable;
|
|
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
|
|
|
|
qry->hasSubLinks = pstate->p_hasSubLinks;
|
|
qry->hasAggs = pstate->p_hasAggs;
|
|
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
|
|
parseCheckAggregates(pstate, qry);
|
|
|
|
foreach(l, stmt->lockingClause)
|
|
{
|
|
transformLockingClause(qry, (LockingClause *) lfirst(l));
|
|
}
|
|
|
|
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 ...)
|
|
*/
|
|
static Query *
|
|
transformValuesClause(ParseState *pstate, SelectStmt *stmt)
|
|
{
|
|
Query *qry = makeNode(Query);
|
|
List *exprsLists = NIL;
|
|
List **coltype_lists = NULL;
|
|
Oid *coltypes = NULL;
|
|
int sublist_length = -1;
|
|
List *newExprsLists;
|
|
RangeTblEntry *rte;
|
|
RangeTblRef *rtr;
|
|
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->targetList == NIL);
|
|
Assert(stmt->fromClause == NIL);
|
|
Assert(stmt->whereClause == NULL);
|
|
Assert(stmt->groupClause == NIL);
|
|
Assert(stmt->havingClause == NULL);
|
|
Assert(stmt->op == SETOP_NONE);
|
|
|
|
/*
|
|
* For each row of VALUES, transform the raw expressions and gather type
|
|
* information. This is also a handy place to reject DEFAULT nodes, which
|
|
* the grammar allows for simplicity.
|
|
*/
|
|
foreach(lc, stmt->valuesLists)
|
|
{
|
|
List *sublist = (List *) lfirst(lc);
|
|
|
|
/* Do basic expression transformation (same as a ROW() expr) */
|
|
sublist = transformExpressionList(pstate, sublist);
|
|
|
|
/*
|
|
* 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 arrays for column-type info */
|
|
coltype_lists = (List **) palloc0(sublist_length * sizeof(List *));
|
|
coltypes = (Oid *) palloc0(sublist_length * sizeof(Oid));
|
|
}
|
|
else if (sublist_length != list_length(sublist))
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("VALUES lists must all be the same length")));
|
|
}
|
|
|
|
exprsLists = lappend(exprsLists, sublist);
|
|
|
|
i = 0;
|
|
foreach(lc2, sublist)
|
|
{
|
|
Node *col = (Node *) lfirst(lc2);
|
|
|
|
if (IsA(col, SetToDefault))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("DEFAULT can only appear in a VALUES list within INSERT")));
|
|
coltype_lists[i] = lappend_oid(coltype_lists[i], exprType(col));
|
|
i++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Now resolve the common types of the columns, and coerce everything to
|
|
* those types.
|
|
*/
|
|
for (i = 0; i < sublist_length; i++)
|
|
{
|
|
coltypes[i] = select_common_type(coltype_lists[i], "VALUES");
|
|
}
|
|
|
|
newExprsLists = NIL;
|
|
foreach(lc, exprsLists)
|
|
{
|
|
List *sublist = (List *) lfirst(lc);
|
|
List *newsublist = NIL;
|
|
|
|
i = 0;
|
|
foreach(lc2, sublist)
|
|
{
|
|
Node *col = (Node *) lfirst(lc2);
|
|
|
|
col = coerce_to_common_type(pstate, col, coltypes[i], "VALUES");
|
|
newsublist = lappend(newsublist, col);
|
|
i++;
|
|
}
|
|
|
|
newExprsLists = lappend(newExprsLists, newsublist);
|
|
}
|
|
|
|
/*
|
|
* Generate the VALUES RTE
|
|
*/
|
|
rte = addRangeTableEntryForValues(pstate, newExprsLists, NULL, true);
|
|
rtr = makeNode(RangeTblRef);
|
|
/* assume new rte is at end */
|
|
rtr->rtindex = list_length(pstate->p_rtable);
|
|
Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
|
|
pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
|
|
pstate->p_varnamespace = lappend(pstate->p_varnamespace, rte);
|
|
|
|
/*
|
|
* Generate a targetlist as though expanding "*"
|
|
*/
|
|
Assert(pstate->p_next_resno == 1);
|
|
qry->targetList = expandRelAttrs(pstate, rte, rtr->rtindex, 0);
|
|
|
|
/*
|
|
* The grammar allows attaching ORDER BY, LIMIT, and FOR UPDATE to a
|
|
* VALUES, so cope.
|
|
*/
|
|
qry->sortClause = transformSortClause(pstate,
|
|
stmt->sortClause,
|
|
&qry->targetList,
|
|
true /* fix unknowns */ );
|
|
|
|
qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
|
|
"OFFSET");
|
|
qry->limitCount = transformLimitClause(pstate, stmt->limitCount,
|
|
"LIMIT");
|
|
|
|
if (stmt->lockingClause)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES")));
|
|
|
|
/* handle any CREATE TABLE AS spec */
|
|
if (stmt->intoClause)
|
|
{
|
|
qry->intoClause = stmt->intoClause;
|
|
if (stmt->intoClause->colNames)
|
|
applyColumnNames(qry->targetList, stmt->intoClause->colNames);
|
|
}
|
|
|
|
/*
|
|
* There mustn't have been any table references in the expressions, else
|
|
* strange things would happen, like Cartesian products of those tables
|
|
* with the VALUES list. We have to check this after parsing ORDER BY et
|
|
* al since those could insert more junk.
|
|
*/
|
|
if (list_length(pstate->p_joinlist) != 1)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("VALUES must not contain table references")));
|
|
|
|
/*
|
|
* Another thing we can't currently support is NEW/OLD references in rules
|
|
* --- seems we'd need something like SQL99's LATERAL construct to ensure
|
|
* that the values would be available while evaluating the VALUES RTE.
|
|
* This is a shame. FIXME
|
|
*/
|
|
if (list_length(pstate->p_rtable) != 1 &&
|
|
contain_vars_of_level((Node *) newExprsLists, 0))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("VALUES must not contain OLD or NEW references"),
|
|
errhint("Use SELECT ... UNION ALL ... instead.")));
|
|
|
|
qry->rtable = pstate->p_rtable;
|
|
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
|
|
|
|
qry->hasSubLinks = pstate->p_hasSubLinks;
|
|
/* aggregates not allowed (but subselects are okay) */
|
|
if (pstate->p_hasAggs)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_GROUPING_ERROR),
|
|
errmsg("cannot use aggregate function in VALUES")));
|
|
|
|
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 *intoColNames = NIL;
|
|
List *sortClause;
|
|
Node *limitOffset;
|
|
Node *limitCount;
|
|
List *lockingClause;
|
|
Node *node;
|
|
ListCell *left_tlist,
|
|
*lct,
|
|
*lcm,
|
|
*l;
|
|
List *targetvars,
|
|
*targetnames,
|
|
*sv_relnamespace,
|
|
*sv_varnamespace,
|
|
*sv_rtable;
|
|
RangeTblEntry *jrte;
|
|
int tllen;
|
|
|
|
qry->commandType = CMD_SELECT;
|
|
|
|
/*
|
|
* Find leftmost leaf SelectStmt; extract the one-time-only items from it
|
|
* and from the top-level node.
|
|
*/
|
|
leftmostSelect = stmt->larg;
|
|
while (leftmostSelect && leftmostSelect->op != SETOP_NONE)
|
|
leftmostSelect = leftmostSelect->larg;
|
|
Assert(leftmostSelect && IsA(leftmostSelect, SelectStmt) &&
|
|
leftmostSelect->larg == NULL);
|
|
if (leftmostSelect->intoClause)
|
|
{
|
|
qry->intoClause = leftmostSelect->intoClause;
|
|
intoColNames = leftmostSelect->intoClause->colNames;
|
|
}
|
|
|
|
/* clear this to prevent complaints in transformSetOperationTree() */
|
|
leftmostSelect->intoClause = NULL;
|
|
|
|
/*
|
|
* These are not one-time, exactly, but we want to process them 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;
|
|
|
|
stmt->sortClause = NIL;
|
|
stmt->limitOffset = NULL;
|
|
stmt->limitCount = NULL;
|
|
stmt->lockingClause = NIL;
|
|
|
|
/* We don't support FOR UPDATE/SHARE with set ops at the moment. */
|
|
if (lockingClause)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
|
|
|
|
/*
|
|
* Recursively transform the components of the tree.
|
|
*/
|
|
sostmt = (SetOperationStmt *) transformSetOperationTree(pstate, stmt);
|
|
Assert(sostmt && IsA(sostmt, SetOperationStmt));
|
|
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 of topmost set operation. Also
|
|
* make lists of the dummy vars and their names for use in parsing ORDER
|
|
* BY.
|
|
*
|
|
* Note: we use leftmostRTI as the varno of the dummy variables. It
|
|
* shouldn't matter too much which RT index they have, as long as they
|
|
* have one that corresponds to a real RT entry; else funny things may
|
|
* happen when the tree is mashed by rule rewriting.
|
|
*/
|
|
qry->targetList = NIL;
|
|
targetvars = NIL;
|
|
targetnames = NIL;
|
|
left_tlist = list_head(leftmostQuery->targetList);
|
|
|
|
forboth(lct, sostmt->colTypes, lcm, sostmt->colTypmods)
|
|
{
|
|
Oid colType = lfirst_oid(lct);
|
|
int32 colTypmod = lfirst_int(lcm);
|
|
TargetEntry *lefttle = (TargetEntry *) lfirst(left_tlist);
|
|
char *colName;
|
|
TargetEntry *tle;
|
|
Expr *expr;
|
|
|
|
Assert(!lefttle->resjunk);
|
|
colName = pstrdup(lefttle->resname);
|
|
expr = (Expr *) makeVar(leftmostRTI,
|
|
lefttle->resno,
|
|
colType,
|
|
colTypmod,
|
|
0);
|
|
tle = makeTargetEntry(expr,
|
|
(AttrNumber) pstate->p_next_resno++,
|
|
colName,
|
|
false);
|
|
qry->targetList = lappend(qry->targetList, tle);
|
|
targetvars = lappend(targetvars, expr);
|
|
targetnames = lappend(targetnames, makeString(colName));
|
|
left_tlist = lnext(left_tlist);
|
|
}
|
|
|
|
/*
|
|
* As a first step towards supporting sort clauses that are expressions
|
|
* using the output columns, generate a varnamespace entry that makes the
|
|
* output columns visible. A Join RTE node is handy for this, since we
|
|
* can easily control the Vars generated upon matches.
|
|
*
|
|
* Note: we don't yet do anything useful with such cases, but at least
|
|
* "ORDER BY upper(foo)" will draw the right error message rather than
|
|
* "foo not found".
|
|
*/
|
|
jrte = addRangeTableEntryForJoin(NULL,
|
|
targetnames,
|
|
JOIN_INNER,
|
|
targetvars,
|
|
NULL,
|
|
false);
|
|
|
|
sv_rtable = pstate->p_rtable;
|
|
pstate->p_rtable = list_make1(jrte);
|
|
|
|
sv_relnamespace = pstate->p_relnamespace;
|
|
pstate->p_relnamespace = NIL; /* no qualified names allowed */
|
|
|
|
sv_varnamespace = pstate->p_varnamespace;
|
|
pstate->p_varnamespace = list_make1(jrte);
|
|
|
|
/*
|
|
* 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,
|
|
false /* no unknowns expected */ );
|
|
|
|
pstate->p_rtable = sv_rtable;
|
|
pstate->p_relnamespace = sv_relnamespace;
|
|
pstate->p_varnamespace = sv_varnamespace;
|
|
|
|
if (tllen != list_length(qry->targetList))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("invalid UNION/INTERSECT/EXCEPT ORDER BY clause"),
|
|
errdetail("Only result column names can be used, not expressions or functions."),
|
|
errhint("Add the expression/function to every SELECT, or move the UNION into a FROM clause.")));
|
|
|
|
qry->limitOffset = transformLimitClause(pstate, limitOffset,
|
|
"OFFSET");
|
|
qry->limitCount = transformLimitClause(pstate, limitCount,
|
|
"LIMIT");
|
|
|
|
/*
|
|
* Handle SELECT INTO/CREATE TABLE AS.
|
|
*
|
|
* Any column names from CREATE TABLE AS need to be attached to both the
|
|
* top level and the leftmost subquery. We do not do this earlier because
|
|
* we do *not* want sortClause processing to be affected.
|
|
*/
|
|
if (intoColNames)
|
|
{
|
|
applyColumnNames(qry->targetList, intoColNames);
|
|
applyColumnNames(leftmostQuery->targetList, intoColNames);
|
|
}
|
|
|
|
qry->rtable = pstate->p_rtable;
|
|
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
|
|
|
|
qry->hasSubLinks = pstate->p_hasSubLinks;
|
|
qry->hasAggs = pstate->p_hasAggs;
|
|
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
|
|
parseCheckAggregates(pstate, qry);
|
|
|
|
foreach(l, lockingClause)
|
|
{
|
|
transformLockingClause(qry, (LockingClause *) lfirst(l));
|
|
}
|
|
|
|
return qry;
|
|
}
|
|
|
|
/*
|
|
* transformSetOperationTree
|
|
* Recursively transform leaves and internal nodes of a set-op tree
|
|
*/
|
|
static Node *
|
|
transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
|
|
{
|
|
bool isLeaf;
|
|
|
|
Assert(stmt && IsA(stmt, SelectStmt));
|
|
|
|
/*
|
|
* 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")));
|
|
/* We don't support FOR UPDATE/SHARE with set ops at the moment. */
|
|
if (stmt->lockingClause)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
|
|
|
|
/*
|
|
* If an internal node of a set-op tree has ORDER BY, UPDATE, or LIMIT
|
|
* 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)
|
|
isLeaf = true;
|
|
else
|
|
isLeaf = false;
|
|
}
|
|
|
|
if (isLeaf)
|
|
{
|
|
/* Process leaf SELECT */
|
|
Query *selectQuery;
|
|
char selectName[32];
|
|
RangeTblEntry *rte;
|
|
RangeTblRef *rtr;
|
|
|
|
/*
|
|
* Transform SelectStmt into a Query.
|
|
*
|
|
* Note: previously transformed sub-queries don't affect the parsing
|
|
* of this sub-query, because they are not in the toplevel pstate's
|
|
* namespace list.
|
|
*/
|
|
selectQuery = parse_sub_analyze((Node *) stmt, pstate);
|
|
|
|
/*
|
|
* Check for bogus references to Vars on the current query level (but
|
|
* upper-level references are okay). Normally this can't happen
|
|
* because the namespace will be empty, but it could happen if we are
|
|
* inside a rule.
|
|
*/
|
|
if (pstate->p_relnamespace || pstate->p_varnamespace)
|
|
{
|
|
if (contain_vars_of_level((Node *) selectQuery, 1))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
|
|
errmsg("UNION/INTERSECT/EXCEPT member statement cannot refer to other relations of same query level")));
|
|
}
|
|
|
|
/*
|
|
* Make the leaf query be a subquery in the top-level rangetable.
|
|
*/
|
|
snprintf(selectName, sizeof(selectName), "*SELECT* %d",
|
|
list_length(pstate->p_rtable) + 1);
|
|
rte = addRangeTableEntryForSubquery(pstate,
|
|
selectQuery,
|
|
makeAlias(selectName, NIL),
|
|
false);
|
|
|
|
/*
|
|
* Return a RangeTblRef to replace the SelectStmt in the set-op tree.
|
|
*/
|
|
rtr = makeNode(RangeTblRef);
|
|
/* assume new rte is at end */
|
|
rtr->rtindex = list_length(pstate->p_rtable);
|
|
Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
|
|
return (Node *) rtr;
|
|
}
|
|
else
|
|
{
|
|
/* Process an internal node (set operation node) */
|
|
SetOperationStmt *op = makeNode(SetOperationStmt);
|
|
List *lcoltypes;
|
|
List *rcoltypes;
|
|
List *lcoltypmods;
|
|
List *rcoltypmods;
|
|
ListCell *lct;
|
|
ListCell *rct;
|
|
ListCell *lcm;
|
|
ListCell *rcm;
|
|
const char *context;
|
|
|
|
context = (stmt->op == SETOP_UNION ? "UNION" :
|
|
(stmt->op == SETOP_INTERSECT ? "INTERSECT" :
|
|
"EXCEPT"));
|
|
|
|
op->op = stmt->op;
|
|
op->all = stmt->all;
|
|
|
|
/*
|
|
* Recursively transform the child nodes.
|
|
*/
|
|
op->larg = transformSetOperationTree(pstate, stmt->larg);
|
|
op->rarg = transformSetOperationTree(pstate, stmt->rarg);
|
|
|
|
/*
|
|
* Verify that the two children have the same number of non-junk
|
|
* columns, and determine the types of the merged output columns.
|
|
*/
|
|
getSetColTypes(pstate, op->larg, &lcoltypes, &lcoltypmods);
|
|
getSetColTypes(pstate, op->rarg, &rcoltypes, &rcoltypmods);
|
|
if (list_length(lcoltypes) != list_length(rcoltypes))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("each %s query must have the same number of columns",
|
|
context)));
|
|
Assert(list_length(lcoltypes) == list_length(lcoltypmods));
|
|
Assert(list_length(rcoltypes) == list_length(rcoltypmods));
|
|
|
|
op->colTypes = NIL;
|
|
op->colTypmods = NIL;
|
|
/* don't have a "foreach4", so chase two of the lists by hand */
|
|
lcm = list_head(lcoltypmods);
|
|
rcm = list_head(rcoltypmods);
|
|
forboth(lct, lcoltypes, rct, rcoltypes)
|
|
{
|
|
Oid lcoltype = lfirst_oid(lct);
|
|
Oid rcoltype = lfirst_oid(rct);
|
|
int32 lcoltypmod = lfirst_int(lcm);
|
|
int32 rcoltypmod = lfirst_int(rcm);
|
|
Oid rescoltype;
|
|
int32 rescoltypmod;
|
|
|
|
/* select common type, same as CASE et al */
|
|
rescoltype = select_common_type(list_make2_oid(lcoltype, rcoltype),
|
|
context);
|
|
/* if same type and same typmod, use typmod; else default */
|
|
if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
|
|
rescoltypmod = lcoltypmod;
|
|
else
|
|
rescoltypmod = -1;
|
|
op->colTypes = lappend_oid(op->colTypes, rescoltype);
|
|
op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
|
|
|
|
lcm = lnext(lcm);
|
|
rcm = lnext(rcm);
|
|
}
|
|
|
|
return (Node *) op;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* getSetColTypes
|
|
* Get output column types/typmods of an (already transformed) set-op node
|
|
*/
|
|
static void
|
|
getSetColTypes(ParseState *pstate, Node *node,
|
|
List **colTypes, List **colTypmods)
|
|
{
|
|
*colTypes = NIL;
|
|
*colTypmods = NIL;
|
|
if (IsA(node, RangeTblRef))
|
|
{
|
|
RangeTblRef *rtr = (RangeTblRef *) node;
|
|
RangeTblEntry *rte = rt_fetch(rtr->rtindex, pstate->p_rtable);
|
|
Query *selectQuery = rte->subquery;
|
|
ListCell *tl;
|
|
|
|
Assert(selectQuery != NULL);
|
|
/* Get types of non-junk columns */
|
|
foreach(tl, selectQuery->targetList)
|
|
{
|
|
TargetEntry *tle = (TargetEntry *) lfirst(tl);
|
|
|
|
if (tle->resjunk)
|
|
continue;
|
|
*colTypes = lappend_oid(*colTypes,
|
|
exprType((Node *) tle->expr));
|
|
*colTypmods = lappend_int(*colTypmods,
|
|
exprTypmod((Node *) tle->expr));
|
|
}
|
|
}
|
|
else if (IsA(node, SetOperationStmt))
|
|
{
|
|
SetOperationStmt *op = (SetOperationStmt *) node;
|
|
|
|
/* Result already computed during transformation of node */
|
|
Assert(op->colTypes != NIL);
|
|
*colTypes = op->colTypes;
|
|
*colTypmods = op->colTypmods;
|
|
}
|
|
else
|
|
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
|
|
}
|
|
|
|
/*
|
|
* Attach column names from a ColumnDef list to a TargetEntry list
|
|
* (for CREATE TABLE AS)
|
|
*/
|
|
static void
|
|
applyColumnNames(List *dst, List *src)
|
|
{
|
|
ListCell *dst_item;
|
|
ListCell *src_item;
|
|
|
|
src_item = list_head(src);
|
|
|
|
foreach(dst_item, dst)
|
|
{
|
|
TargetEntry *d = (TargetEntry *) lfirst(dst_item);
|
|
ColumnDef *s;
|
|
|
|
/* junk targets don't count */
|
|
if (d->resjunk)
|
|
continue;
|
|
|
|
/* fewer ColumnDefs than target entries is OK */
|
|
if (src_item == NULL)
|
|
break;
|
|
|
|
s = (ColumnDef *) lfirst(src_item);
|
|
src_item = lnext(src_item);
|
|
|
|
d->resname = pstrdup(s->colname);
|
|
}
|
|
|
|
/* more ColumnDefs than target entries is not OK */
|
|
if (src_item != NULL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("CREATE TABLE AS specifies too many column names")));
|
|
}
|
|
|
|
|
|
/*
|
|
* transformUpdateStmt -
|
|
* transforms an update statement
|
|
*/
|
|
static Query *
|
|
transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
|
|
{
|
|
Query *qry = makeNode(Query);
|
|
Node *qual;
|
|
ListCell *origTargetList;
|
|
ListCell *tl;
|
|
|
|
qry->commandType = CMD_UPDATE;
|
|
pstate->p_is_update = true;
|
|
|
|
qry->resultRelation = setTargetTable(pstate, stmt->relation,
|
|
interpretInhOption(stmt->relation->inhOpt),
|
|
true,
|
|
ACL_UPDATE);
|
|
|
|
/*
|
|
* the FROM clause is non-standard SQL syntax. We used to be able to do
|
|
* this with REPLACE in POSTQUEL so we keep the feature.
|
|
*/
|
|
transformFromClause(pstate, stmt->fromClause);
|
|
|
|
qry->targetList = transformTargetList(pstate, stmt->targetList);
|
|
|
|
qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
|
|
|
|
qry->returningList = transformReturningList(pstate, stmt->returningList);
|
|
|
|
qry->rtable = pstate->p_rtable;
|
|
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
|
|
|
|
qry->hasSubLinks = pstate->p_hasSubLinks;
|
|
|
|
/*
|
|
* Top-level aggregates are simply disallowed in UPDATE, per spec. (From
|
|
* an implementation point of view, this is forced because the implicit
|
|
* ctid reference would otherwise be an ungrouped variable.)
|
|
*/
|
|
if (pstate->p_hasAggs)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_GROUPING_ERROR),
|
|
errmsg("cannot use aggregate function in UPDATE")));
|
|
|
|
/*
|
|
* Now we are done with SELECT-like processing, and can get on with
|
|
* transforming the target list to match the UPDATE target columns.
|
|
*/
|
|
|
|
/* Prepare to assign non-conflicting resnos to resjunk attributes */
|
|
if (pstate->p_next_resno <= pstate->p_target_relation->rd_rel->relnatts)
|
|
pstate->p_next_resno = pstate->p_target_relation->rd_rel->relnatts + 1;
|
|
|
|
/* Prepare non-junk columns for assignment to target table */
|
|
origTargetList = list_head(stmt->targetList);
|
|
|
|
foreach(tl, qry->targetList)
|
|
{
|
|
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 (origTargetList == NULL)
|
|
elog(ERROR, "UPDATE target count mismatch --- internal error");
|
|
origTarget = (ResTarget *) lfirst(origTargetList);
|
|
Assert(IsA(origTarget, ResTarget));
|
|
|
|
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);
|
|
|
|
origTargetList = lnext(origTargetList);
|
|
}
|
|
if (origTargetList != NULL)
|
|
elog(ERROR, "UPDATE target count mismatch --- internal error");
|
|
|
|
return qry;
|
|
}
|
|
|
|
/*
|
|
* transformReturningList -
|
|
* handle a RETURNING clause in INSERT/UPDATE/DELETE
|
|
*/
|
|
static List *
|
|
transformReturningList(ParseState *pstate, List *returningList)
|
|
{
|
|
List *rlist;
|
|
int save_next_resno;
|
|
bool save_hasAggs;
|
|
int length_rtable;
|
|
|
|
if (returningList == NIL)
|
|
return NIL; /* nothing to do */
|
|
|
|
/*
|
|
* We need to assign resnos starting at one in the RETURNING list. Save
|
|
* and restore the main tlist's value of p_next_resno, just in case
|
|
* someone looks at it later (probably won't happen).
|
|
*/
|
|
save_next_resno = pstate->p_next_resno;
|
|
pstate->p_next_resno = 1;
|
|
|
|
/* save other state so that we can detect disallowed stuff */
|
|
save_hasAggs = pstate->p_hasAggs;
|
|
pstate->p_hasAggs = false;
|
|
length_rtable = list_length(pstate->p_rtable);
|
|
|
|
/* transform RETURNING identically to a SELECT targetlist */
|
|
rlist = transformTargetList(pstate, returningList);
|
|
|
|
/* check for disallowed stuff */
|
|
|
|
/* aggregates not allowed (but subselects are okay) */
|
|
if (pstate->p_hasAggs)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_GROUPING_ERROR),
|
|
errmsg("cannot use aggregate function in RETURNING")));
|
|
|
|
/* no new relation references please */
|
|
if (list_length(pstate->p_rtable) != length_rtable)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("RETURNING cannot contain references to other relations")));
|
|
|
|
/* mark column origins */
|
|
markTargetListOrigins(pstate, rlist);
|
|
|
|
/* restore state */
|
|
pstate->p_next_resno = save_next_resno;
|
|
pstate->p_hasAggs = save_hasAggs;
|
|
|
|
return rlist;
|
|
}
|
|
|
|
|
|
/*
|
|
* transformDeclareCursorStmt -
|
|
* transform a DECLARE CURSOR Statement
|
|
*
|
|
* DECLARE CURSOR is a hybrid case: it's an optimizable statement (in fact not
|
|
* significantly different from a SELECT) as far as parsing/rewriting/planning
|
|
* are concerned, but it's not passed to the executor and so in that sense is
|
|
* a utility statement. We transform it into a Query exactly as if it were
|
|
* a SELECT, then stick the original DeclareCursorStmt into the utilityStmt
|
|
* field to carry the cursor name and options.
|
|
*/
|
|
static Query *
|
|
transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
|
|
{
|
|
Query *result;
|
|
|
|
/*
|
|
* Don't allow both SCROLL and NO SCROLL to be specified
|
|
*/
|
|
if ((stmt->options & CURSOR_OPT_SCROLL) &&
|
|
(stmt->options & CURSOR_OPT_NO_SCROLL))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
|
|
errmsg("cannot specify both SCROLL and NO SCROLL")));
|
|
|
|
result = transformStmt(pstate, stmt->query);
|
|
|
|
if (!IsA(result, Query) ||
|
|
result->commandType != CMD_SELECT ||
|
|
result->utilityStmt != NULL)
|
|
elog(ERROR, "unexpected non-SELECT command in cursor statement");
|
|
|
|
/* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
|
|
if (result->intoClause)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
|
|
errmsg("DECLARE CURSOR cannot specify INTO")));
|
|
|
|
/* FOR UPDATE and WITH HOLD are not compatible */
|
|
if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_HOLD))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("DECLARE CURSOR WITH HOLD ... FOR UPDATE/SHARE is not supported"),
|
|
errdetail("Holdable cursors must be READ ONLY.")));
|
|
|
|
/* FOR UPDATE and SCROLL are not compatible */
|
|
if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_SCROLL))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("DECLARE SCROLL CURSOR ... FOR UPDATE/SHARE is not supported"),
|
|
errdetail("Scrollable cursors must be READ ONLY.")));
|
|
|
|
/* FOR UPDATE and INSENSITIVE are not compatible */
|
|
if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_INSENSITIVE))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("DECLARE INSENSITIVE CURSOR ... FOR UPDATE/SHARE is not supported"),
|
|
errdetail("Insensitive cursors must be READ ONLY.")));
|
|
|
|
/* We won't need the raw querytree any more */
|
|
stmt->query = NULL;
|
|
|
|
result->utilityStmt = (Node *) stmt;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
* transformExplainStmt -
|
|
* transform an EXPLAIN Statement
|
|
*
|
|
* EXPLAIN is just like other utility statements in that we emit it as a
|
|
* CMD_UTILITY Query node with no transformation of the raw parse tree.
|
|
* However, if p_variableparams is set, it could be that the client is
|
|
* expecting us to resolve parameter types in something like
|
|
* EXPLAIN SELECT * FROM tab WHERE col = $1
|
|
* To deal with such cases, we run parse analysis and throw away the result;
|
|
* this is a bit grotty but not worth contorting the rest of the system for.
|
|
* (The approach we use for DECLARE CURSOR won't work because the statement
|
|
* being explained isn't necessarily a SELECT, and in particular might rewrite
|
|
* to multiple parsetrees.)
|
|
*/
|
|
static Query *
|
|
transformExplainStmt(ParseState *pstate, ExplainStmt *stmt)
|
|
{
|
|
Query *result;
|
|
|
|
if (pstate->p_variableparams)
|
|
{
|
|
/* Since parse analysis scribbles on its input, copy the tree first! */
|
|
(void) transformStmt(pstate, copyObject(stmt->query));
|
|
}
|
|
|
|
/* Now return the untransformed command as a utility Query */
|
|
result = makeNode(Query);
|
|
result->commandType = CMD_UTILITY;
|
|
result->utilityStmt = (Node *) stmt;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/* exported so planner can check again after rewriting, query pullup, etc */
|
|
void
|
|
CheckSelectLocking(Query *qry)
|
|
{
|
|
if (qry->setOperations)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
|
|
if (qry->distinctClause != NIL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("SELECT FOR UPDATE/SHARE is not allowed with DISTINCT clause")));
|
|
if (qry->groupClause != NIL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("SELECT FOR UPDATE/SHARE is not allowed with GROUP BY clause")));
|
|
if (qry->havingQual != NULL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("SELECT FOR UPDATE/SHARE is not allowed with HAVING clause")));
|
|
if (qry->hasAggs)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("SELECT FOR UPDATE/SHARE is not allowed with aggregate functions")));
|
|
}
|
|
|
|
/*
|
|
* Transform a FOR UPDATE/SHARE clause
|
|
*
|
|
* This basically involves replacing names by integer relids.
|
|
*
|
|
* NB: if you need to change this, see also markQueryForLocking()
|
|
* in rewriteHandler.c.
|
|
*/
|
|
static void
|
|
transformLockingClause(Query *qry, LockingClause *lc)
|
|
{
|
|
List *lockedRels = lc->lockedRels;
|
|
ListCell *l;
|
|
ListCell *rt;
|
|
Index i;
|
|
LockingClause *allrels;
|
|
|
|
CheckSelectLocking(qry);
|
|
|
|
/* make a clause we can pass down to subqueries to select all rels */
|
|
allrels = makeNode(LockingClause);
|
|
allrels->lockedRels = NIL; /* indicates all rels */
|
|
allrels->forUpdate = lc->forUpdate;
|
|
allrels->noWait = lc->noWait;
|
|
|
|
if (lockedRels == NIL)
|
|
{
|
|
/* all regular tables used in query */
|
|
i = 0;
|
|
foreach(rt, qry->rtable)
|
|
{
|
|
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
|
|
|
|
++i;
|
|
switch (rte->rtekind)
|
|
{
|
|
case RTE_RELATION:
|
|
applyLockingClause(qry, i, lc->forUpdate, lc->noWait);
|
|
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
|
|
break;
|
|
case RTE_SUBQUERY:
|
|
|
|
/*
|
|
* FOR UPDATE/SHARE of subquery is propagated to all of
|
|
* subquery's rels
|
|
*/
|
|
transformLockingClause(rte->subquery, allrels);
|
|
break;
|
|
default:
|
|
/* ignore JOIN, SPECIAL, FUNCTION RTEs */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* just the named tables */
|
|
foreach(l, lockedRels)
|
|
{
|
|
char *relname = strVal(lfirst(l));
|
|
|
|
i = 0;
|
|
foreach(rt, qry->rtable)
|
|
{
|
|
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
|
|
|
|
++i;
|
|
if (strcmp(rte->eref->aliasname, relname) == 0)
|
|
{
|
|
switch (rte->rtekind)
|
|
{
|
|
case RTE_RELATION:
|
|
applyLockingClause(qry, i,
|
|
lc->forUpdate, lc->noWait);
|
|
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
|
|
break;
|
|
case RTE_SUBQUERY:
|
|
|
|
/*
|
|
* FOR UPDATE/SHARE of subquery is propagated to
|
|
* all of subquery's rels
|
|
*/
|
|
transformLockingClause(rte->subquery, allrels);
|
|
break;
|
|
case RTE_JOIN:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a join")));
|
|
break;
|
|
case RTE_SPECIAL:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to NEW or OLD")));
|
|
break;
|
|
case RTE_FUNCTION:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a function")));
|
|
break;
|
|
case RTE_VALUES:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES")));
|
|
break;
|
|
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),
|
|
errmsg("relation \"%s\" in FOR UPDATE/SHARE clause not found in FROM clause",
|
|
relname)));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Record locking info for a single rangetable item
|
|
*/
|
|
void
|
|
applyLockingClause(Query *qry, Index rtindex, bool forUpdate, bool noWait)
|
|
{
|
|
RowMarkClause *rc;
|
|
|
|
/* Check for pre-existing entry for same rtindex */
|
|
if ((rc = get_rowmark(qry, rtindex)) != NULL)
|
|
{
|
|
/*
|
|
* If the same RTE is specified both FOR UPDATE and FOR SHARE, treat
|
|
* it as FOR UPDATE. (Reasonable, since you can't take both a shared
|
|
* and exclusive lock at the same time; it'll end up being exclusive
|
|
* anyway.)
|
|
*
|
|
* We also consider that NOWAIT wins if it's specified both ways. 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.)
|
|
*/
|
|
rc->forUpdate |= forUpdate;
|
|
rc->noWait |= noWait;
|
|
return;
|
|
}
|
|
|
|
/* Make a new RowMarkClause */
|
|
rc = makeNode(RowMarkClause);
|
|
rc->rti = rtindex;
|
|
rc->forUpdate = forUpdate;
|
|
rc->noWait = noWait;
|
|
qry->rowMarks = lappend(qry->rowMarks, rc);
|
|
}
|
|
|
|
|
|
/*
|
|
* Traverse a fully-analyzed tree to verify that parameter symbols
|
|
* match their types. We need this because some Params might still
|
|
* be UNKNOWN, if there wasn't anything to force their coercion,
|
|
* and yet other instances seen later might have gotten coerced.
|
|
*/
|
|
static bool
|
|
check_parameter_resolution_walker(Node *node,
|
|
check_parameter_resolution_context *context)
|
|
{
|
|
if (node == NULL)
|
|
return false;
|
|
if (IsA(node, Param))
|
|
{
|
|
Param *param = (Param *) node;
|
|
|
|
if (param->paramkind == PARAM_EXTERN)
|
|
{
|
|
int paramno = param->paramid;
|
|
|
|
if (paramno <= 0 || /* shouldn't happen, but... */
|
|
paramno > context->numParams)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_PARAMETER),
|
|
errmsg("there is no parameter $%d", paramno)));
|
|
|
|
if (param->paramtype != context->paramTypes[paramno - 1])
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_AMBIGUOUS_PARAMETER),
|
|
errmsg("could not determine data type of parameter $%d",
|
|
paramno)));
|
|
}
|
|
return false;
|
|
}
|
|
if (IsA(node, Query))
|
|
{
|
|
/* Recurse into RTE subquery or not-yet-planned sublink subquery */
|
|
return query_tree_walker((Query *) node,
|
|
check_parameter_resolution_walker,
|
|
(void *) context, 0);
|
|
}
|
|
return expression_tree_walker(node, check_parameter_resolution_walker,
|
|
(void *) context);
|
|
}
|