* optimizable statements.
*
*
- * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.384 2008/12/13 02:00:19 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.404 2010/09/18 18:37:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
+#include "access/sysattr.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_coerce.h"
#include "parser/parse_cte.h"
#include "parser/parse_oper.h"
+#include "parser/parse_param.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#include "parser/parsetree.h"
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
static List *transformInsertRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos);
+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,
- List **colInfo);
+ bool isTopLevel, List **colInfo);
+static void determineRecursiveColTypes(ParseState *pstate,
+ Node *larg, List *lcolinfo);
static void applyColumnNames(List *dst, List *src);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static List *transformReturningList(ParseState *pstate, List *returningList);
DeclareCursorStmt *stmt);
static Query *transformExplainStmt(ParseState *pstate,
ExplainStmt *stmt);
-static void transformLockingClause(ParseState *pstate,
- Query *qry, LockingClause *lc);
-static bool check_parameter_resolution_walker(Node *node, ParseState *pstate);
+static void transformLockingClause(ParseState *pstate, Query *qry,
+ LockingClause *lc, bool pushedDown);
/*
ParseState *pstate = make_parsestate(NULL);
Query *query;
- Assert(sourceText != NULL); /* required as of 8.4 */
+ Assert(sourceText != NULL); /* required as of 8.4 */
pstate->p_sourcetext = sourceText;
- pstate->p_paramtypes = paramTypes;
- pstate->p_numparams = numParams;
- pstate->p_variableparams = false;
+
+ if (numParams > 0)
+ parse_fixed_parameters(pstate, paramTypes, numParams);
query = transformStmt(pstate, parseTree);
ParseState *pstate = make_parsestate(NULL);
Query *query;
- Assert(sourceText != NULL); /* required as of 8.4 */
+ Assert(sourceText != NULL); /* required as of 8.4 */
pstate->p_sourcetext = sourceText;
- pstate->p_paramtypes = *paramTypes;
- pstate->p_numparams = *numParams;
- pstate->p_variableparams = true;
+
+ parse_variable_parameters(pstate, paramTypes, numParams);
query = transformStmt(pstate, parseTree);
/* make sure all is well with parameter types */
- if (pstate->p_numparams > 0)
- check_parameter_resolution_walker((Node *) query, pstate);
-
- *paramTypes = pstate->p_paramtypes;
- *numParams = pstate->p_numparams;
+ check_variable_parameters(pstate, query);
free_parsestate(pstate);
* Entry point for recursively analyzing a sub-statement.
*/
Query *
-parse_sub_analyze(Node *parseTree, ParseState *parentParseState)
+parse_sub_analyze(Node *parseTree, ParseState *parentParseState,
+ CommonTableExpr *parentCTE,
+ bool locked_from_parent)
{
ParseState *pstate = make_parsestate(parentParseState);
Query *query;
+ pstate->p_parent_cte = parentCTE;
+ pstate->p_locked_from_parent = locked_from_parent;
+
query = transformStmt(pstate, parseTree);
free_parsestate(pstate);
* Returns true if a snapshot must be set before doing parse analysis
* on the given raw parse tree.
*
- * Classification here should match transformStmt().
+ * Classification here should match transformStmt(); but we also have to
+ * allow a NULL input (for Parse/Bind of an empty query string).
*/
bool
analyze_requires_snapshot(Node *parseTree)
{
bool result;
+ if (parseTree == NULL)
+ return false;
+
switch (nodeTag(parseTree))
{
/*
break;
case T_ExplainStmt:
- /*
- * We only need a snapshot in varparams case, but it doesn't seem
- * worth complicating this function's API to distinguish that.
- */
+ /* yes, because we must analyze the contained statement */
result = true;
break;
default:
- /* utility statements don't have any active parse analysis */
+ /* other utility statements don't have any real parse analysis */
result = false;
break;
}
qry->hasAggs = pstate->p_hasAggs;
if (pstate->p_hasAggs)
parseCheckAggregates(pstate, qry);
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ if (pstate->p_hasWindowFuncs)
+ parseCheckWindowFuncs(pstate, qry);
return qry;
}
* bugs of just that nature...)
*/
sub_pstate->p_rtable = sub_rtable;
+ sub_pstate->p_joinexprs = NIL; /* sub_rtable has no joins */
sub_pstate->p_relnamespace = sub_relnamespace;
sub_pstate->p_varnamespace = sub_varnamespace;
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("INSERT ... SELECT cannot specify INTO"),
parser_errposition(pstate,
- exprLocation((Node *) selectQuery->intoClause))));
+ exprLocation((Node *) selectQuery->intoClause))));
/*
* Make the source be a subquery in the INSERT's rangetable, and add
expr = tle->expr;
else
{
- Var *var = makeVar(rtr->rtindex,
- tle->resno,
- exprType((Node *) tle->expr),
- exprTypmod((Node *) tle->expr),
- 0);
+ Var *var = makeVarFromTargetEntry(rtr->rtindex, tle);
+
var->location = exprLocation((Node *) tle->expr);
expr = (Expr *) var;
}
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VALUES must not contain table references"),
parser_errposition(pstate,
- locate_var_of_level((Node *) exprsLists, 0))));
+ locate_var_of_level((Node *) exprsLists, 0))));
/*
* Another thing we can't currently support is NEW/OLD references in
errmsg("VALUES must not contain OLD or NEW references"),
errhint("Use SELECT ... UNION ALL ... instead."),
parser_errposition(pstate,
- locate_var_of_level((Node *) exprsLists, 0))));
+ locate_var_of_level((Node *) exprsLists, 0))));
/*
* Generate the VALUES RTE
/*
* Generate query's target list using the computed list of expressions.
+ * Also, mark all the target columns as needing insert permissions.
*/
+ rte = pstate->p_target_rangetblentry;
qry->targetList = NIL;
icols = list_head(icolumns);
attnos = list_head(attrnos);
{
Expr *expr = (Expr *) lfirst(lc);
ResTarget *col;
+ AttrNumber attr_num;
TargetEntry *tle;
col = (ResTarget *) lfirst(icols);
Assert(IsA(col, ResTarget));
+ attr_num = (AttrNumber) lfirst_int(attnos);
tle = makeTargetEntry(expr,
- (AttrNumber) lfirst_int(attnos),
+ attr_num,
col->name,
false);
qry->targetList = lappend(qry->targetList, tle);
+ rte->modifiedCols = bms_add_member(rte->modifiedCols,
+ attr_num - FirstLowInvalidHeapAttributeNumber);
+
icols = lnext(icols);
attnos = lnext(attnos);
}
errmsg("cannot use aggregate function in VALUES"),
parser_errposition(pstate,
locate_agg_of_level((Node *) qry, 0))));
+ if (pstate->p_hasWindowFuncs)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("cannot use window function in VALUES"),
+ parser_errposition(pstate,
+ locate_windowfunc((Node *) qry))));
return qry;
}
errmsg("INSERT has more expressions than target columns"),
parser_errposition(pstate,
exprLocation(list_nth(exprlist,
- list_length(icolumns))))));
+ 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))))));
+ list_length(exprlist))))));
+ }
/*
* Prepare columns for assignment to target table.
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 -
qry->commandType = CMD_SELECT;
- /* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
- pstate->p_locking_clause = stmt->lockingClause;
-
- /* process the WITH clause */
+ /* process the WITH clause independently of all else */
if (stmt->withClause)
{
qry->hasRecursive = stmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, stmt->withClause);
}
+ /* 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 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.
+ * 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,
- true /* fix unknowns */ );
+ true /* fix unknowns */ ,
+ false /* allow SQL92 rules */ );
qry->groupClause = transformGroupClause(pstate,
stmt->groupClause,
&qry->targetList,
- qry->sortClause);
+ qry->sortClause,
+ false /* allow SQL92 rules */ );
if (stmt->distinctClause == NIL)
{
/* We had SELECT DISTINCT */
qry->distinctClause = transformDistinctClause(pstate,
&qry->targetList,
- qry->sortClause);
+ qry->sortClause,
+ false);
qry->hasDistinctOn = false;
}
else
qry->limitCount = transformLimitClause(pstate, stmt->limitCount,
"LIMIT");
+ /* transform window clauses after we have seen all window functions */
+ qry->windowClause = transformWindowDefinitions(pstate,
+ pstate->p_windowdefs,
+ &qry->targetList);
+
/* handle any SELECT INTO/CREATE TABLE AS spec */
if (stmt->intoClause)
{
qry->hasAggs = pstate->p_hasAggs;
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
parseCheckAggregates(pstate, qry);
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ if (pstate->p_hasWindowFuncs)
+ parseCheckWindowFuncs(pstate, qry);
foreach(l, stmt->lockingClause)
{
- transformLockingClause(pstate, qry, (LockingClause *) lfirst(l));
+ transformLockingClause(pstate, qry,
+ (LockingClause *) lfirst(l), false);
}
return qry;
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 */
+ /* process the WITH clause independently of all else */
if (stmt->withClause)
{
qry->hasRecursive = stmt->withClause->recursive;
qry->sortClause = transformSortClause(pstate,
stmt->sortClause,
&qry->targetList,
- true /* fix unknowns */ );
+ true /* fix unknowns */ ,
+ false /* allow SQL92 rules */ );
qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
"OFFSET");
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VALUES must not contain table references"),
parser_errposition(pstate,
- locate_var_of_level((Node *) newExprsLists, 0))));
+ locate_var_of_level((Node *) newExprsLists, 0))));
/*
* Another thing we can't currently support is NEW/OLD references in rules
errmsg("VALUES must not contain OLD or NEW references"),
errhint("Use SELECT ... UNION ALL ... instead."),
parser_errposition(pstate,
- locate_var_of_level((Node *) newExprsLists, 0))));
+ locate_var_of_level((Node *) newExprsLists, 0))));
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in VALUES"),
parser_errposition(pstate,
- locate_agg_of_level((Node *) newExprsLists, 0))));
+ locate_agg_of_level((Node *) newExprsLists, 0))));
+ if (pstate->p_hasWindowFuncs)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("cannot use window function in VALUES"),
+ parser_errposition(pstate,
+ locate_windowfunc((Node *) newExprsLists))));
return qry;
}
List *targetvars,
*targetnames,
*sv_relnamespace,
- *sv_varnamespace,
- *sv_rtable;
+ *sv_varnamespace;
+ int sv_rtable_length;
RangeTblEntry *jrte;
int tllen;
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);
+ }
+
/*
* Find leftmost leaf SelectStmt; extract the one-time-only items from it
* and from the top-level node.
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
- /* process the WITH clause */
- if (stmt->withClause)
- {
- qry->hasRecursive = stmt->withClause->recursive;
- qry->cteList = transformWithClause(pstate, stmt->withClause);
- }
-
/*
* Recursively transform the components of the tree.
*/
sostmt = (SetOperationStmt *) transformSetOperationTree(pstate, stmt,
+ true,
&socolinfo);
Assert(sostmt && IsA(sostmt, SetOperationStmt));
qry->setOperations = (Node *) sostmt;
* "ORDER BY upper(foo)" will draw the right error message rather than
* "foo not found".
*/
- jrte = addRangeTableEntryForJoin(NULL,
+ sv_rtable_length = list_length(pstate->p_rtable);
+
+ jrte = addRangeTableEntryForJoin(pstate,
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 */
qry->sortClause = transformSortClause(pstate,
sortClause,
&qry->targetList,
- false /* no unknowns expected */ );
+ false /* no unknowns expected */ ,
+ false /* allow SQL92 rules */ );
- pstate->p_rtable = sv_rtable;
+ pstate->p_rtable = list_truncate(pstate->p_rtable, sv_rtable_length);
pstate->p_relnamespace = sv_relnamespace;
pstate->p_varnamespace = sv_varnamespace;
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)))));
+ exprLocation(list_nth(qry->targetList, tllen)))));
qry->limitOffset = transformLimitClause(pstate, limitOffset,
"OFFSET");
qry->hasAggs = pstate->p_hasAggs;
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
parseCheckAggregates(pstate, qry);
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ if (pstate->p_hasWindowFuncs)
+ parseCheckWindowFuncs(pstate, qry);
foreach(l, lockingClause)
{
- transformLockingClause(pstate, qry, (LockingClause *) lfirst(l));
+ transformLockingClause(pstate, qry,
+ (LockingClause *) lfirst(l), false);
}
return qry;
*/
static Node *
transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
- List **colInfo)
+ bool isTopLevel, List **colInfo)
{
bool isLeaf;
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT"),
parser_errposition(pstate,
- exprLocation((Node *) stmt->intoClause))));
+ exprLocation((Node *) stmt->intoClause))));
/* We don't support FOR UPDATE/SHARE with set ops at the moment. */
if (stmt->lockingClause)
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
+ * If an internal node of a set-op tree has ORDER BY, LIMIT, or FOR UPDATE
* 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.
* of this sub-query, because they are not in the toplevel pstate's
* namespace list.
*/
- selectQuery = parse_sub_analyze((Node *) stmt, pstate);
+ selectQuery = parse_sub_analyze((Node *) stmt, pstate, NULL, false);
/*
* Check for bogus references to Vars on the current query level (but
(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))));
+ locate_var_of_level((Node *) selectQuery, 1))));
}
/*
op->all = stmt->all;
/*
- * Recursively transform the child nodes.
+ * Recursively transform the left child node.
*/
op->larg = transformSetOperationTree(pstate, stmt->larg,
+ false,
&lcolinfo);
+
+ /*
+ * If we are processing a recursive union query, now is the time to
+ * examine the non-recursive term's output columns and mark the
+ * containing CTE as having those result columns. We should do this
+ * only at the topmost setop of the CTE, of course.
+ */
+ if (isTopLevel &&
+ pstate->p_parent_cte &&
+ pstate->p_parent_cte->cterecursive)
+ determineRecursiveColTypes(pstate, op->larg, lcolinfo);
+
+ /*
+ * Recursively transform the right child node.
+ */
op->rarg = transformSetOperationTree(pstate, stmt->rarg,
+ false,
&rcolinfo);
/*
op->groupClauses = NIL;
forboth(lci, lcolinfo, rci, rcolinfo)
{
- Node *lcolinfo = (Node *) lfirst(lci);
- Node *rcolinfo = (Node *) lfirst(rci);
- Oid lcoltype = exprType(lcolinfo);
- Oid rcoltype = exprType(rcolinfo);
- int32 lcoltypmod = exprTypmod(lcolinfo);
- int32 rcoltypmod = exprTypmod(rcolinfo);
+ Node *lcolnode = (Node *) lfirst(lci);
+ Node *rcolnode = (Node *) lfirst(rci);
+ Oid lcoltype = exprType(lcolnode);
+ Oid rcoltype = exprType(rcolnode);
+ int32 lcoltypmod = exprTypmod(lcolnode);
+ int32 rcoltypmod = exprTypmod(rcolnode);
Node *bestexpr;
- SetToDefault *rescolinfo;
+ SetToDefault *rescolnode;
Oid rescoltype;
int32 rescoltypmod;
/* select common type, same as CASE et al */
rescoltype = select_common_type(pstate,
- list_make2(lcolinfo, rcolinfo),
+ list_make2(lcolnode, rcolnode),
context,
&bestexpr);
/* if same type and same typmod, use typmod; else default */
else
rescoltypmod = -1;
- /* verify the coercions are actually possible */
- (void) coerce_to_common_type(pstate, lcolinfo,
- rescoltype, context);
- (void) coerce_to_common_type(pstate, rcolinfo,
- rescoltype, context);
+ /*
+ * 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.
+ *
+ * The if-tests might look wrong, but they are correct: we should
+ * verify if the input is non-UNKNOWN *or* if it is an UNKNOWN
+ * Const (to verify the literal is valid for the target data type)
+ * or Param (to possibly resolve the Param's type). We should do
+ * nothing if the input is say an UNKNOWN Var, which can happen in
+ * some cases. The planner is sometimes able to fold the Var to a
+ * constant before it has to coerce the type, so failing now would
+ * just break cases that might work.
+ */
+ if (lcoltype != UNKNOWNOID ||
+ IsA(lcolnode, Const) ||IsA(lcolnode, Param))
+ (void) coerce_to_common_type(pstate, lcolnode,
+ rescoltype, context);
+ if (rcoltype != UNKNOWNOID ||
+ IsA(rcolnode, Const) ||IsA(rcolnode, Param))
+ (void) coerce_to_common_type(pstate, rcolnode,
+ rescoltype, context);
/* emit results */
- rescolinfo = makeNode(SetToDefault);
- rescolinfo->typeId = rescoltype;
- rescolinfo->typeMod = rescoltypmod;
- rescolinfo->location = exprLocation(bestexpr);
- *colInfo = lappend(*colInfo, rescolinfo);
+ rescolnode = makeNode(SetToDefault);
+ rescolnode->typeId = rescoltype;
+ rescolnode->typeMod = rescoltypmod;
+ rescolnode->location = exprLocation(bestexpr);
+ *colInfo = lappend(*colInfo, rescolnode);
op->colTypes = lappend_oid(op->colTypes, rescoltype);
op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
ParseCallbackState pcbstate;
setup_parser_errposition_callback(&pcbstate, pstate,
- rescolinfo->location);
+ rescolnode->location);
/* determine the eqop and optional sortop */
get_sort_group_operators(rescoltype,
}
}
+/*
+ * 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 *lcolinfo)
+{
+ Node *node;
+ int leftmostRTI;
+ Query *leftmostQuery;
+ List *targetList;
+ ListCell *left_tlist;
+ ListCell *lci;
+ 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;
+ left_tlist = list_head(leftmostQuery->targetList);
+ next_resno = 1;
+
+ foreach(lci, lcolinfo)
+ {
+ Expr *lcolexpr = (Expr *) lfirst(lci);
+ TargetEntry *lefttle = (TargetEntry *) lfirst(left_tlist);
+ char *colName;
+ TargetEntry *tle;
+
+ Assert(!lefttle->resjunk);
+ colName = pstrdup(lefttle->resname);
+ tle = makeTargetEntry(lcolexpr,
+ next_resno++,
+ colName,
+ false);
+ targetList = lappend(targetList, tle);
+ left_tlist = lnext(left_tlist);
+ }
+
+ /* Now build CTE's output column info using dummy targetlist */
+ analyzeCTETargetList(pstate, pstate->p_parent_cte, targetList);
+}
+
/*
* Attach column names from a ColumnDef list to a TargetEntry list
* (for CREATE TABLE AS)
transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
{
Query *qry = makeNode(Query);
+ RangeTblEntry *target_rte;
Node *qual;
ListCell *origTargetList;
ListCell *tl;
errmsg("cannot use aggregate function in UPDATE"),
parser_errposition(pstate,
locate_agg_of_level((Node *) qry, 0))));
+ if (pstate->p_hasWindowFuncs)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("cannot use window function in UPDATE"),
+ parser_errposition(pstate,
+ locate_windowfunc((Node *) qry))));
/*
* Now we are done with SELECT-like processing, and can get on with
pstate->p_next_resno = pstate->p_target_relation->rd_rel->relnatts + 1;
/* Prepare non-junk columns for assignment to target table */
+ target_rte = pstate->p_target_rangetblentry;
origTargetList = list_head(stmt->targetList);
foreach(tl, qry->targetList)
origTarget->indirection,
origTarget->location);
+ /* Mark the target column as requiring update permissions */
+ target_rte->modifiedCols = bms_add_member(target_rte->modifiedCols,
+ attrno - FirstLowInvalidHeapAttributeNumber);
+
origTargetList = lnext(origTargetList);
}
if (origTargetList != NULL)
List *rlist;
int save_next_resno;
bool save_hasAggs;
+ bool save_hasWindowFuncs;
int length_rtable;
if (returningList == NIL)
/* save other state so that we can detect disallowed stuff */
save_hasAggs = pstate->p_hasAggs;
pstate->p_hasAggs = false;
+ save_hasWindowFuncs = pstate->p_hasWindowFuncs;
+ pstate->p_hasWindowFuncs = false;
length_rtable = list_length(pstate->p_rtable);
/* transform RETURNING identically to a SELECT targetlist */
errmsg("cannot use aggregate function in RETURNING"),
parser_errposition(pstate,
locate_agg_of_level((Node *) rlist, 0))));
+ if (pstate->p_hasWindowFuncs)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("cannot use window function in RETURNING"),
+ parser_errposition(pstate,
+ locate_windowfunc((Node *) rlist))));
/* no new relation references please */
if (list_length(pstate->p_rtable) != length_rtable)
{
- int vlocation = -1;
- int relid;
+ int vlocation = -1;
+ int relid;
/* try to locate such a reference to point to */
for (relid = length_rtable + 1; relid <= list_length(pstate->p_rtable); relid++)
}
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("RETURNING cannot contain references to other relations"),
+ errmsg("RETURNING cannot contain references to other relations"),
parser_errposition(pstate, vlocation)));
}
/* restore state */
pstate->p_next_resno = save_next_resno;
pstate->p_hasAggs = save_hasAggs;
+ pstate->p_hasWindowFuncs = save_hasWindowFuncs;
return rlist;
}
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("DECLARE CURSOR cannot specify INTO"),
parser_errposition(pstate,
- exprLocation((Node *) result->intoClause))));
+ exprLocation((Node *) result->intoClause))));
/* FOR UPDATE and WITH HOLD are not compatible */
if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_HOLD))
* 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.)
+ * 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;
- if (pstate->p_variableparams)
- {
- /* Since parse analysis scribbles on its input, copy the tree first! */
- (void) transformStmt(pstate, copyObject(stmt->query));
- }
+ /* transform contained query */
+ stmt->query = (Node *) transformStmt(pstate, stmt->query);
- /* Now return the untransformed command as a utility Query */
+ /* represent the command as a utility Query */
result = makeNode(Query);
result->commandType = CMD_UTILITY;
result->utilityStmt = (Node *) stmt;
}
-/* exported so planner can check again after rewriting, query pullup, etc */
+/*
+ * Check for features that are not supported together with FOR UPDATE/SHARE.
+ *
+ * exported so planner can check again after rewriting, query pullup, etc
+ */
void
CheckSelectLocking(Query *qry)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE is not allowed with aggregate functions")));
+ if (qry->hasWindowFuncs)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SELECT FOR UPDATE/SHARE is not allowed with window functions")));
+ if (expression_returns_set((Node *) qry->targetList))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SELECT FOR UPDATE/SHARE is not allowed with set-returning functions in the target list")));
}
/*
* This basically involves replacing names by integer relids.
*
* NB: if you need to change this, see also markQueryForLocking()
- * in rewriteHandler.c, and isLockedRel() in parse_relation.c.
+ * in rewriteHandler.c, and isLockedRefname() in parse_relation.c.
*/
static void
-transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc)
+transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
+ bool pushedDown)
{
List *lockedRels = lc->lockedRels;
ListCell *l;
switch (rte->rtekind)
{
case RTE_RELATION:
- applyLockingClause(qry, i, lc->forUpdate, lc->noWait);
+ applyLockingClause(qry, i,
+ lc->forUpdate, lc->noWait, pushedDown);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
break;
case RTE_SUBQUERY:
+ applyLockingClause(qry, i,
+ lc->forUpdate, lc->noWait, pushedDown);
/*
* FOR UPDATE/SHARE of subquery is propagated to all of
- * subquery's rels
+ * 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);
- break;
- case RTE_CTE:
- {
- /*
- * We allow FOR UPDATE/SHARE of a WITH query to be
- * propagated into the WITH, but it doesn't seem
- * very sane to allow this for a reference to an
- * outer-level WITH. And it definitely wouldn't
- * work for a self-reference, since we're not done
- * analyzing the CTE anyway.
- */
- CommonTableExpr *cte;
-
- if (rte->ctelevelsup > 0 || rte->self_reference)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE cannot be applied to an outer-level WITH query")));
- cte = GetCTEForRTE(pstate, rte, -1);
- /* should be analyzed by now */
- Assert(IsA(cte->ctequery, Query));
- transformLockingClause(pstate,
- (Query *) cte->ctequery,
- allrels);
- }
+ transformLockingClause(pstate, rte->subquery,
+ allrels, true);
break;
default:
- /* ignore JOIN, SPECIAL, FUNCTION RTEs */
+ /* ignore JOIN, SPECIAL, FUNCTION, VALUES, CTE RTEs */
break;
}
}
{
case RTE_RELATION:
applyLockingClause(qry, i,
- lc->forUpdate, lc->noWait);
+ lc->forUpdate, lc->noWait,
+ pushedDown);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
break;
case RTE_SUBQUERY:
-
- /*
- * FOR UPDATE/SHARE of subquery is propagated to
- * all of subquery's rels
- */
- transformLockingClause(pstate, rte->subquery, allrels);
+ applyLockingClause(qry, i,
+ lc->forUpdate, lc->noWait,
+ pushedDown);
+ /* see comment above */
+ transformLockingClause(pstate, rte->subquery,
+ allrels, true);
break;
case RTE_JOIN:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a join"),
- parser_errposition(pstate, thisrel->location)));
+ parser_errposition(pstate, thisrel->location)));
break;
case RTE_SPECIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to NEW or OLD"),
- parser_errposition(pstate, thisrel->location)));
+ parser_errposition(pstate, thisrel->location)));
break;
case RTE_FUNCTION:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a function"),
- parser_errposition(pstate, thisrel->location)));
+ parser_errposition(pstate, thisrel->location)));
break;
case RTE_VALUES:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES"),
- parser_errposition(pstate, thisrel->location)));
+ parser_errposition(pstate, thisrel->location)));
break;
case RTE_CTE:
- {
- /*
- * We allow FOR UPDATE/SHARE of a WITH query
- * to be propagated into the WITH, but it
- * doesn't seem very sane to allow this for a
- * reference to an outer-level WITH. And it
- * definitely wouldn't work for a
- * self-reference, since we're not done
- * analyzing the CTE anyway.
- */
- CommonTableExpr *cte;
-
- if (rte->ctelevelsup > 0 || rte->self_reference)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE cannot be applied to an outer-level WITH query"),
- parser_errposition(pstate, thisrel->location)));
- cte = GetCTEForRTE(pstate, rte, -1);
- /* should be analyzed by now */
- Assert(IsA(cte->ctequery, Query));
- transformLockingClause(pstate,
- (Query *) cte->ctequery,
- allrels);
- }
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a WITH query"),
+ parser_errposition(pstate, thisrel->location)));
break;
default:
elog(ERROR, "unrecognized RTE type: %d",
* Record locking info for a single rangetable item
*/
void
-applyLockingClause(Query *qry, Index rtindex, bool forUpdate, bool noWait)
+applyLockingClause(Query *qry, Index rtindex,
+ bool forUpdate, bool noWait, bool pushedDown)
{
RowMarkClause *rc;
+ /* 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_rowmark(qry, rtindex)) != NULL)
+ if ((rc = get_parse_rowmark(qry, rtindex)) != NULL)
{
/*
* If the same RTE is specified both FOR UPDATE and FOR SHARE, treat
* 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.)
+ *
+ * And of course pushedDown becomes false if any clause is explicit.
*/
rc->forUpdate |= forUpdate;
rc->noWait |= noWait;
+ rc->pushedDown &= pushedDown;
return;
}
/* Make a new RowMarkClause */
rc = makeNode(RowMarkClause);
rc->rti = rtindex;
- rc->prti = rtindex;
rc->forUpdate = forUpdate;
rc->noWait = noWait;
- rc->isParent = false;
+ rc->pushedDown = pushedDown;
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, ParseState *pstate)
-{
- 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 > pstate->p_numparams)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_PARAMETER),
- errmsg("there is no parameter $%d", paramno),
- parser_errposition(pstate, param->location)));
-
- if (param->paramtype != pstate->p_paramtypes[paramno - 1])
- ereport(ERROR,
- (errcode(ERRCODE_AMBIGUOUS_PARAMETER),
- errmsg("could not determine data type of parameter $%d",
- paramno),
- parser_errposition(pstate, param->location)));
- }
- 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 *) pstate, 0);
- }
- return expression_tree_walker(node, check_parameter_resolution_walker,
- (void *) pstate);
-}