* optimizable statements.
*
*
- * Portions Copyright (c) 1996-2009, 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.398 2009/12/16 22:24:13 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.404 2010/09/18 18:37:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
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,
bool isTopLevel, List **colInfo);
static void determineRecursiveColTypes(ParseState *pstate,
- Node *larg, List *lcolinfo);
+ 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 *transformExplainStmt(ParseState *pstate,
ExplainStmt *stmt);
static void transformLockingClause(ParseState *pstate, Query *qry,
- LockingClause *lc, bool pushedDown);
+ LockingClause *lc, bool pushedDown);
/*
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;
}
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;
list_length(icolumns))))));
if (stmtcols != NIL &&
list_length(exprlist) < list_length(icolumns))
+ {
+ /*
+ * We can get here for cases like INSERT ... SELECT (a,b,c) FROM ...
+ * where the user accidentally created a RowExpr instead of separate
+ * columns. Add a suitable hint if that seems to be the problem,
+ * because the main error message is quite misleading for this case.
+ * (If there's no stmtcols, you'll get something about data type
+ * mismatch, which is less misleading so we don't worry about giving
+ * a hint in that case.)
+ */
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("INSERT has more target columns than expressions"),
+ ((list_length(exprlist) == 1 &&
+ count_rowexpr_columns(pstate, linitial(exprlist)) ==
+ list_length(icolumns)) ?
+ errhint("The insertion source is a row expression containing the same number of columns expected by the INSERT. Did you accidentally use extra parentheses?") : 0),
parser_errposition(pstate,
exprLocation(list_nth(icolumns,
list_length(exprlist))))));
+ }
/*
* Prepare columns for assignment to target table.
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;
-
- /* make WINDOW info available for window functions, too */
- pstate->p_windowdefs = stmt->windowClause;
-
- /* 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);
qry->sortClause = transformSortClause(pstate,
stmt->sortClause,
&qry->targetList,
- true /* fix unknowns */,
- false /* allow SQL92 rules */);
+ true /* fix unknowns */ ,
+ false /* allow SQL92 rules */ );
qry->groupClause = transformGroupClause(pstate,
stmt->groupClause,
&qry->targetList,
qry->sortClause,
- false /* allow SQL92 rules */);
+ false /* allow SQL92 rules */ );
if (stmt->distinctClause == NIL)
{
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 */,
- false /* allow SQL92 rules */);
+ true /* fix unknowns */ ,
+ false /* allow SQL92 rules */ );
qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
"OFFSET");
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.
*/
qry->sortClause = transformSortClause(pstate,
sortClause,
&qry->targetList,
- false /* no unknowns expected */,
- false /* allow SQL92 rules */);
+ false /* no unknowns expected */ ,
+ false /* allow SQL92 rules */ );
pstate->p_rtable = list_truncate(pstate->p_rtable, sv_rtable_length);
pstate->p_relnamespace = sv_relnamespace;
&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
+ * 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.
*/
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.
+ * 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
+ * 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))
+ IsA(lcolnode, Const) ||IsA(lcolnode, Param))
(void) coerce_to_common_type(pstate, lcolnode,
rescoltype, context);
if (rcoltype != UNKNOWNOID ||
- IsA(rcolnode, Const) || IsA(rcolnode, Param))
+ IsA(rcolnode, Const) ||IsA(rcolnode, Param))
(void) coerce_to_common_type(pstate, rcolnode,
rescoltype, context);
Assert(leftmostQuery != NULL);
/*
- * Generate dummy targetlist using column names of leftmost select
- * and dummy result expressions of the non-recursive term.
+ * 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);
* 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_coerce_param_hook 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_coerce_param_hook != NULL)
- {
- /* 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;
case RTE_SUBQUERY:
applyLockingClause(qry, i,
lc->forUpdate, lc->noWait, pushedDown);
+
/*
* FOR UPDATE/SHARE of subquery is propagated to all of
- * subquery's rels, too. We could do this later (based
- * on the marking of the subquery RTE) but it is convenient
- * to have local knowledge in each query level about
- * which rels need to be opened with RowShareLock.
+ * subquery's rels, too. We could do this later (based on
+ * the marking of the subquery RTE) but it is convenient
+ * to have local knowledge in each query level about which
+ * rels need to be opened with RowShareLock.
*/
transformLockingClause(pstate, rte->subquery,
allrels, true);