* optimizable statements.
*
*
- * Portions Copyright (c) 1996-2007, 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.367 2007/06/23 22:12:51 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 "optimizer/clauses.h"
+#include "nodes/nodeFuncs.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
#include "parser/parse_agg.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
-#include "parser/parse_expr.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"
-
-
-typedef struct
-{
- Oid *paramTypes;
- int numParams;
-} check_parameter_resolution_context;
+#include "rewrite/rewriteManip.h"
+#include "utils/rel.h"
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 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);
-static void getSetColTypes(ParseState *pstate, Node *node,
- List **colTypes, List **colTypmods);
+static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
+ 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);
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);
+ ExplainStmt *stmt);
+static void transformLockingClause(ParseState *pstate, Query *qry,
+ LockingClause *lc, bool pushedDown);
/*
* 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
+ * 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.
*/
ParseState *pstate = make_parsestate(NULL);
Query *query;
+ 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;
- pstate->p_sourcetext = sourceText;
- pstate->p_paramtypes = *paramTypes;
- pstate->p_numparams = *numParams;
- pstate->p_variableparams = true;
+ Assert(sourceText != NULL); /* required as of 8.4 */
- query = transformStmt(pstate, parseTree);
+ pstate->p_sourcetext = sourceText;
- *paramTypes = pstate->p_paramtypes;
- *numParams = pstate->p_numparams;
+ parse_variable_parameters(pstate, paramTypes, numParams);
- free_parsestate(pstate);
+ query = transformStmt(pstate, parseTree);
/* make sure all is well with parameter types */
- if (*numParams > 0)
- {
- check_parameter_resolution_context context;
+ check_variable_parameters(pstate, query);
- context.paramTypes = *paramTypes;
- context.numParams = *numParams;
- check_parameter_resolution_walker((Node *) query, &context);
- }
+ free_parsestate(pstate);
return query;
}
* 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);
return result;
}
+/*
+ * analyze_requires_snapshot
+ * Returns true if a snapshot must be set before doing parse analysis
+ * on the given raw parse tree.
+ *
+ * Classification here should match transformStmt(); but we also have to
+ * allow a NULL input (for Parse/Bind of an empty query string).
+ */
+bool
+analyze_requires_snapshot(Node *parseTree)
+{
+ bool result;
+
+ if (parseTree == NULL)
+ return false;
+
+ switch (nodeTag(parseTree))
+ {
+ /*
+ * Optimizable statements
+ */
+ case T_InsertStmt:
+ case T_DeleteStmt:
+ case T_UpdateStmt:
+ case T_SelectStmt:
+ result = true;
+ break;
+
+ /*
+ * Special cases
+ */
+ case T_DeclareCursorStmt:
+ /* yes, because it's analyzed just like SELECT */
+ result = true;
+ break;
+
+ case T_ExplainStmt:
+ /* yes, because we must analyze the contained statement */
+ result = true;
+ break;
+
+ default:
+ /* other utility statements don't have any real parse analysis */
+ result = false;
+ break;
+ }
+
+ return result;
+}
+
/*
* transformDeleteStmt -
* transforms a Delete Statement
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;
}
pstate->p_relnamespace = NIL;
sub_varnamespace = pstate->p_varnamespace;
pstate->p_varnamespace = NIL;
+ /* There can't be any outer WITH to worry about */
+ Assert(pstate->p_ctenamespace == NIL);
}
else
{
* 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;
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 (!IsA(selectQuery, Query) ||
+ selectQuery->commandType != CMD_SELECT ||
+ selectQuery->utilityStmt != NULL)
+ elog(ERROR, "unexpected non-SELECT command in INSERT ... SELECT");
if (selectQuery->intoClause)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("INSERT ... SELECT cannot specify INTO")));
+ errmsg("INSERT ... SELECT cannot specify INTO"),
+ parser_errposition(pstate,
+ exprLocation((Node *) selectQuery->intoClause))));
/*
* Make the source be a subquery in the INSERT's rangetable, and add
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);
+ {
+ Var *var = makeVarFromTargetEntry(rtr->rtindex, tle);
+
+ var->location = exprLocation((Node *) tle->expr);
+ expr = (Expr *) var;
+ }
exprList = lappend(exprList, expr);
}
List *exprsLists = NIL;
int sublist_length = -1;
+ /* process the WITH clause */
+ if (selectStmt->withClause)
+ {
+ qry->hasRecursive = selectStmt->withClause->recursive;
+ qry->cteList = transformWithClause(pstate, selectStmt->withClause);
+ }
+
foreach(lc, selectStmt->valuesLists)
{
List *sublist = (List *) lfirst(lc);
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("VALUES lists must all be the same length")));
+ errmsg("VALUES lists must all be the same length"),
+ parser_errposition(pstate,
+ exprLocation((Node *) sublist))));
}
/* Prepare row for assignment to target table */
if (pstate->p_joinlist != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("VALUES must not contain table references")));
+ errmsg("VALUES must not contain table references"),
+ parser_errposition(pstate,
+ locate_var_of_level((Node *) exprsLists, 0))));
/*
* Another thing we can't currently support is NEW/OLD references in
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VALUES must not contain OLD or NEW references"),
- errhint("Use SELECT ... UNION ALL ... instead.")));
+ errhint("Use SELECT ... UNION ALL ... instead."),
+ parser_errposition(pstate,
+ locate_var_of_level((Node *) exprsLists, 0))));
/*
* Generate the VALUES RTE
/*
* Generate list of Vars referencing the RTE
*/
- expandRTE(rte, rtr->rtindex, 0, false, NULL, &exprList);
+ expandRTE(rte, rtr->rtindex, 0, -1, false, NULL, &exprList);
}
else
{
Assert(list_length(valuesLists) == 1);
+ /* process the WITH clause */
+ if (selectStmt->withClause)
+ {
+ qry->hasRecursive = selectStmt->withClause->recursive;
+ qry->cteList = transformWithClause(pstate, selectStmt->withClause);
+ }
+
/* Do basic expression transformation (same as a ROW() expr) */
exprList = transformExpressionList(pstate,
(List *) linitial(valuesLists));
/*
* 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);
}
if (pstate->p_hasAggs)
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
- errmsg("cannot use aggregate function in VALUES")));
+ 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;
}
if (list_length(exprlist) > list_length(icolumns))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("INSERT has more expressions than target columns")));
+ errmsg("INSERT has more expressions than target columns"),
+ parser_errposition(pstate,
+ exprLocation(list_nth(exprlist,
+ list_length(icolumns))))));
if (stmtcols != NIL &&
list_length(exprlist) < list_length(icolumns))
+ {
+ /*
+ * We can get here for cases like INSERT ... SELECT (a,b,c) FROM ...
+ * where the user accidentally created a RowExpr instead of separate
+ * columns. Add a suitable hint if that seems to be the problem,
+ * because the main error message is quite misleading for this case.
+ * (If there's no stmtcols, you'll get something about data type
+ * mismatch, which is less misleading so we don't worry about giving
+ * a hint in that case.)
+ */
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("INSERT has more target columns than expressions")));
+ errmsg("INSERT has more target columns than expressions"),
+ ((list_length(exprlist) == 1 &&
+ count_rowexpr_columns(pstate, linitial(exprlist)) ==
+ list_length(icolumns)) ?
+ errhint("The insertion source is a row expression containing the same number of columns expected by the INSERT. Did you accidentally use extra parentheses?") : 0),
+ parser_errposition(pstate,
+ exprLocation(list_nth(icolumns,
+ list_length(exprlist))))));
+ }
/*
* Prepare columns for assignment to target table.
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;
+ /* 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.
+ * 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 */ );
- qry->distinctClause = transformDistinctClause(pstate,
- stmt->distinctClause,
- &qry->targetList,
- &qry->sortClause);
+ if (stmt->distinctClause == NIL)
+ {
+ qry->distinctClause = NIL;
+ qry->hasDistinctOn = false;
+ }
+ else if (linitial(stmt->distinctClause) == NULL)
+ {
+ /* We had SELECT DISTINCT */
+ qry->distinctClause = transformDistinctClause(pstate,
+ &qry->targetList,
+ qry->sortClause,
+ false);
+ qry->hasDistinctOn = false;
+ }
+ else
+ {
+ /* We had SELECT DISTINCT ON */
+ qry->distinctClause = transformDistinctOnClause(pstate,
+ stmt->distinctClause,
+ &qry->targetList,
+ qry->sortClause);
+ qry->hasDistinctOn = true;
+ }
+ /* transform LIMIT */
qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
"OFFSET");
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(qry, (LockingClause *) lfirst(l));
+ transformLockingClause(pstate, qry,
+ (LockingClause *) lfirst(l), false);
}
return qry;
{
Query *qry = makeNode(Query);
List *exprsLists = NIL;
- List **coltype_lists = NULL;
+ List **colexprs = NULL;
Oid *coltypes = NULL;
int sublist_length = -1;
List *newExprsLists;
Assert(stmt->whereClause == NULL);
Assert(stmt->groupClause == NIL);
Assert(stmt->havingClause == NULL);
+ Assert(stmt->windowClause == NIL);
Assert(stmt->op == SETOP_NONE);
+ /* process the WITH clause independently of all else */
+ if (stmt->withClause)
+ {
+ qry->hasRecursive = stmt->withClause->recursive;
+ qry->cteList = transformWithClause(pstate, stmt->withClause);
+ }
+
/*
* For each row of VALUES, transform the raw expressions and gather type
* information. This is also a handy place to reject DEFAULT nodes, which
{
/* 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 *));
+ /* and allocate arrays for per-column info */
+ colexprs = (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")));
+ errmsg("VALUES lists must all be the same length"),
+ parser_errposition(pstate,
+ exprLocation((Node *) sublist))));
}
exprsLists = lappend(exprsLists, sublist);
+ /* Check for DEFAULT and build per-column expression lists */
i = 0;
foreach(lc2, sublist)
{
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));
+ errmsg("DEFAULT can only appear in a VALUES list within INSERT"),
+ parser_errposition(pstate, exprLocation(col))));
+ colexprs[i] = lappend(colexprs[i], col);
i++;
}
}
*/
for (i = 0; i < sublist_length; i++)
{
- coltypes[i] = select_common_type(coltype_lists[i], "VALUES");
+ coltypes[i] = select_common_type(pstate, colexprs[i], "VALUES", NULL);
}
newExprsLists = NIL;
* Generate a targetlist as though expanding "*"
*/
Assert(pstate->p_next_resno == 1);
- qry->targetList = expandRelAttrs(pstate, rte, rtr->rtindex, 0);
+ qry->targetList = expandRelAttrs(pstate, rte, rtr->rtindex, 0, -1);
/*
* The grammar allows attaching ORDER BY, LIMIT, and FOR UPDATE to a
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");
if (list_length(pstate->p_joinlist) != 1)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("VALUES must not contain table references")));
+ errmsg("VALUES must not contain table references"),
+ parser_errposition(pstate,
+ locate_var_of_level((Node *) newExprsLists, 0))));
/*
* Another thing we can't currently support is NEW/OLD references in rules
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VALUES must not contain OLD or NEW references"),
- errhint("Use SELECT ... UNION ALL ... instead.")));
+ errhint("Use SELECT ... UNION ALL ... instead."),
+ parser_errposition(pstate,
+ locate_var_of_level((Node *) newExprsLists, 0))));
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
if (pstate->p_hasAggs)
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
- errmsg("cannot use aggregate function in VALUES")));
+ errmsg("cannot use aggregate function in VALUES"),
+ parser_errposition(pstate,
+ 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;
}
int leftmostRTI;
Query *leftmostQuery;
SetOperationStmt *sostmt;
+ List *socolinfo;
List *intoColNames = NIL;
List *sortClause;
Node *limitOffset;
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.
/*
* Recursively transform the components of the tree.
*/
- sostmt = (SetOperationStmt *) transformSetOperationTree(pstate, stmt);
+ sostmt = (SetOperationStmt *) transformSetOperationTree(pstate, stmt,
+ true,
+ &socolinfo);
Assert(sostmt && IsA(sostmt, SetOperationStmt));
qry->setOperations = (Node *) sostmt;
TargetEntry *lefttle = (TargetEntry *) lfirst(left_tlist);
char *colName;
TargetEntry *tle;
- Expr *expr;
+ Var *var;
Assert(!lefttle->resjunk);
colName = pstrdup(lefttle->resname);
- expr = (Expr *) makeVar(leftmostRTI,
- lefttle->resno,
- colType,
- colTypmod,
- 0);
- tle = makeTargetEntry(expr,
+ var = makeVar(leftmostRTI,
+ lefttle->resno,
+ colType,
+ colTypmod,
+ 0);
+ var->location = exprLocation((Node *) lefttle->expr);
+ tle = makeTargetEntry((Expr *) var,
(AttrNumber) pstate->p_next_resno++,
colName,
false);
qry->targetList = lappend(qry->targetList, tle);
- targetvars = lappend(targetvars, expr);
+ targetvars = lappend(targetvars, var);
targetnames = lappend(targetnames, makeString(colName));
left_tlist = lnext(left_tlist);
}
* "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;
(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.")));
+ errhint("Add the expression/function to every SELECT, or move the UNION into a FROM clause."),
+ parser_errposition(pstate,
+ exprLocation(list_nth(qry->targetList, tllen)))));
qry->limitOffset = transformLimitClause(pstate, limitOffset,
"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(qry, (LockingClause *) lfirst(l));
+ transformLockingClause(pstate, qry,
+ (LockingClause *) lfirst(l), false);
}
return qry;
/*
* transformSetOperationTree
* Recursively transform leaves and internal nodes of a set-op tree
+ *
+ * In addition to returning the transformed node, we return a list of
+ * expression nodes showing the type, typmod, and location (for error messages)
+ * of each output column of the set-op node. This is used only during the
+ * internal recursion of this function. At the upper levels we use
+ * SetToDefault nodes for this purpose, since they carry exactly the fields
+ * needed, but any other expression node type would do as well.
*/
static Node *
-transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
+transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
+ bool isTopLevel, List **colInfo)
{
bool isLeaf;
if (stmt->intoClause)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT")));
+ errmsg("INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT"),
+ parser_errposition(pstate,
+ exprLocation((Node *) stmt->intoClause))));
+
/* We don't support FOR UPDATE/SHARE with set ops at the moment. */
if (stmt->lockingClause)
ereport(ERROR,
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.
char selectName[32];
RangeTblEntry *rte;
RangeTblRef *rtr;
+ ListCell *tl;
/*
* Transform SelectStmt into a 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
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")));
+ errmsg("UNION/INTERSECT/EXCEPT member statement cannot refer to other relations of same query level"),
+ parser_errposition(pstate,
+ locate_var_of_level((Node *) selectQuery, 1))));
+ }
+
+ /*
+ * Extract a list of the result expressions for upper-level checking.
+ */
+ *colInfo = NIL;
+ foreach(tl, selectQuery->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(tl);
+
+ if (!tle->resjunk)
+ *colInfo = lappend(*colInfo, tle->expr);
}
/*
{
/* 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;
+ List *lcolinfo;
+ List *rcolinfo;
+ ListCell *lci;
+ ListCell *rci;
const char *context;
context = (stmt->op == SETOP_UNION ? "UNION" :
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->larg = transformSetOperationTree(pstate, stmt->larg);
- op->rarg = transformSetOperationTree(pstate, stmt->rarg);
+ op->rarg = transformSetOperationTree(pstate, stmt->rarg,
+ false,
+ &rcolinfo);
/*
* 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))
+ if (list_length(lcolinfo) != list_length(rcolinfo))
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));
+ context),
+ parser_errposition(pstate,
+ exprLocation((Node *) rcolinfo))));
+ *colInfo = NIL;
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)
+ op->groupClauses = NIL;
+ forboth(lci, lcolinfo, rci, rcolinfo)
{
- Oid lcoltype = lfirst_oid(lct);
- Oid rcoltype = lfirst_oid(rct);
- int32 lcoltypmod = lfirst_int(lcm);
- int32 rcoltypmod = lfirst_int(rcm);
+ 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 *rescolnode;
Oid rescoltype;
int32 rescoltypmod;
/* select common type, same as CASE et al */
- rescoltype = select_common_type(list_make2_oid(lcoltype, rcoltype),
- context);
+ rescoltype = select_common_type(pstate,
+ list_make2(lcolnode, rcolnode),
+ context,
+ &bestexpr);
/* if same type and same typmod, use typmod; else default */
if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
rescoltypmod = lcoltypmod;
else
rescoltypmod = -1;
+
+ /*
+ * Verify the coercions are actually possible. If not, we'd fail
+ * later anyway, but we want to fail now while we have sufficient
+ * context to produce an error cursor position.
+ *
+ * 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 */
+ 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);
- lcm = lnext(lcm);
- rcm = lnext(rcm);
+ /*
+ * For all cases except UNION ALL, identify the grouping operators
+ * (and, if available, sorting operators) that will be used to
+ * eliminate duplicates.
+ */
+ if (op->op != SETOP_UNION || !op->all)
+ {
+ SortGroupClause *grpcl = makeNode(SortGroupClause);
+ Oid sortop;
+ Oid eqop;
+ ParseCallbackState pcbstate;
+
+ setup_parser_errposition_callback(&pcbstate, pstate,
+ rescolnode->location);
+
+ /* determine the eqop and optional sortop */
+ get_sort_group_operators(rescoltype,
+ false, true, false,
+ &sortop, &eqop, NULL);
+
+ cancel_parser_errposition_callback(&pcbstate);
+
+ /* we don't have a tlist yet, so can't assign sortgrouprefs */
+ grpcl->tleSortGroupRef = 0;
+ grpcl->eqop = eqop;
+ grpcl->sortop = sortop;
+ grpcl->nulls_first = false; /* OK with or without sortop */
+
+ op->groupClauses = lappend(op->groupClauses, grpcl);
+ }
}
return (Node *) op;
}
/*
- * getSetColTypes
- * Get output column types/typmods of an (already transformed) set-op node
+ * Process the outputs of the non-recursive term of a recursive union
+ * to set up the parent CTE's columns
*/
static void
-getSetColTypes(ParseState *pstate, Node *node,
- List **colTypes, List **colTypmods)
+determineRecursiveColTypes(ParseState *pstate, Node *larg, List *lcolinfo)
{
- *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;
+ Node *node;
+ int leftmostRTI;
+ Query *leftmostQuery;
+ List *targetList;
+ ListCell *left_tlist;
+ ListCell *lci;
+ int next_resno;
- Assert(selectQuery != NULL);
- /* Get types of non-junk columns */
- foreach(tl, selectQuery->targetList)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(tl);
+ /*
+ * 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);
- 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))
+ /*
+ * 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)
{
- SetOperationStmt *op = (SetOperationStmt *) node;
+ Expr *lcolexpr = (Expr *) lfirst(lci);
+ TargetEntry *lefttle = (TargetEntry *) lfirst(left_tlist);
+ char *colName;
+ TargetEntry *tle;
- /* Result already computed during transformation of node */
- Assert(op->colTypes != NIL);
- *colTypes = op->colTypes;
- *colTypmods = op->colTypmods;
+ Assert(!lefttle->resjunk);
+ colName = pstrdup(lefttle->resname);
+ tle = makeTargetEntry(lcolexpr,
+ next_resno++,
+ colName,
+ false);
+ targetList = lappend(targetList, tle);
+ left_tlist = lnext(left_tlist);
}
- else
- elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
+
+ /* Now build CTE's output column info using dummy targetlist */
+ analyzeCTETargetList(pstate, pstate->p_parent_cte, targetList);
}
/*
transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
{
Query *qry = makeNode(Query);
+ RangeTblEntry *target_rte;
Node *qual;
ListCell *origTargetList;
ListCell *tl;
if (pstate->p_hasAggs)
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
- errmsg("cannot use aggregate function in UPDATE")));
+ 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 */
if (pstate->p_hasAggs)
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
- errmsg("cannot use aggregate function in RETURNING")));
+ 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;
+
+ /* try to locate such a reference to point to */
+ for (relid = length_rtable + 1; relid <= list_length(pstate->p_rtable); relid++)
+ {
+ vlocation = locate_var_of_relation((Node *) rlist, relid, 0);
+ if (vlocation >= 0)
+ break;
+ }
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)));
+ }
/* mark column origins */
markTargetListOrigins(pstate, rlist);
/* restore state */
pstate->p_next_resno = save_next_resno;
pstate->p_hasAggs = save_hasAggs;
+ pstate->p_hasWindowFuncs = save_hasWindowFuncs;
return rlist;
}
result = transformStmt(pstate, stmt->query);
+ /* Grammar should not have allowed anything but SELECT */
if (!IsA(result, Query) ||
result->commandType != CMD_SELECT ||
result->utilityStmt != NULL)
- elog(ERROR, "unexpected non-SELECT command in cursor statement");
+ elog(ERROR, "unexpected non-SELECT command in DECLARE CURSOR");
/* 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")));
+ errmsg("DECLARE CURSOR cannot specify INTO"),
+ parser_errposition(pstate,
+ exprLocation((Node *) result->intoClause))));
/* 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"),
+ 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;
* 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.
+ * in rewriteHandler.c, and isLockedRefname() in parse_relation.c.
*/
static void
-transformLockingClause(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(rte->subquery, allrels);
+ transformLockingClause(pstate, rte->subquery,
+ allrels, true);
break;
default:
- /* ignore JOIN, SPECIAL, FUNCTION RTEs */
+ /* ignore JOIN, SPECIAL, FUNCTION, VALUES, CTE RTEs */
break;
}
}
/* just the named tables */
foreach(l, lockedRels)
{
- char *relname = strVal(lfirst(l));
+ RangeVar *thisrel = (RangeVar *) lfirst(l);
+
+ /* For simplicity we insist on unqualified alias names here */
+ if (thisrel->catalogname || thisrel->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("SELECT FOR UPDATE/SHARE must specify unqualified relation names"),
+ parser_errposition(pstate, thisrel->location)));
i = 0;
foreach(rt, qry->rtable)
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
++i;
- if (strcmp(rte->eref->aliasname, relname) == 0)
+ if (strcmp(rte->eref->aliasname, thisrel->relname) == 0)
{
switch (rte->rtekind)
{
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(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")));
+ errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a join"),
+ 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")));
+ errmsg("SELECT FOR UPDATE/SHARE cannot be applied to NEW or OLD"),
+ 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")));
+ errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a function"),
+ 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")));
+ errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES"),
+ parser_errposition(pstate, thisrel->location)));
+ break;
+ case RTE_CTE:
+ 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",
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" in FOR UPDATE/SHARE clause not found in FROM clause",
- relname)));
+ thisrel->relname),
+ parser_errposition(pstate, thisrel->location)));
}
}
}
* 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;
}
rc->rti = rtindex;
rc->forUpdate = forUpdate;
rc->noWait = noWait;
+ 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,
- 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);
-}