]> granicus.if.org Git - postgresql/blobdiff - src/backend/parser/analyze.c
Give a suitable HINT when an INSERT's data source is a RowExpr containing
[postgresql] / src / backend / parser / analyze.c
index 408e2a5a40b43262aa40c7a40126f74dd291c986..a1ad3e20e5bf90dfbe46e1a6d969f09d6bb868d6 100644 (file)
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -47,13 +47,14 @@ 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,
                                                  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);
@@ -62,7 +63,7 @@ static Query *transformDeclareCursorStmt(ParseState *pstate,
 static Query *transformExplainStmt(ParseState *pstate,
                                         ExplainStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
-                                                                  LockingClause *lc, bool pushedDown);
+                                          LockingClause *lc, bool pushedDown);
 
 
 /*
@@ -257,16 +258,12 @@ analyze_requires_snapshot(Node *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;
        }
@@ -488,11 +485,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
                                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;
@@ -734,12 +727,27 @@ transformInsertRow(ParseState *pstate, List *exprlist,
                                                                                                  list_length(icolumns))))));
        if (stmtcols != NIL &&
                list_length(exprlist) < list_length(icolumns))
+       {
+               /*
+                * We can get here for cases like INSERT ... SELECT (a,b,c) FROM ...
+                * where the user accidentally created a RowExpr instead of separate
+                * columns.  Add a suitable hint if that seems to be the problem,
+                * because the main error message is quite misleading for this case.
+                * (If there's no stmtcols, you'll get something about data type
+                * mismatch, which is less misleading so we don't worry about giving
+                * a hint in that case.)
+                */
                ereport(ERROR,
                                (errcode(ERRCODE_SYNTAX_ERROR),
                                 errmsg("INSERT has more target columns than expressions"),
+                                ((list_length(exprlist) == 1 &&
+                                  count_rowexpr_columns(pstate, linitial(exprlist)) ==
+                                  list_length(icolumns)) ?
+                                 errhint("The insertion source is a row expression containing the same number of columns expected by the INSERT. Did you accidentally use extra parentheses?") : 0),
                                 parser_errposition(pstate,
                                                                        exprLocation(list_nth(icolumns,
                                                                                                  list_length(exprlist))))));
+       }
 
        /*
         * Prepare columns for assignment to target table.
@@ -770,6 +778,49 @@ transformInsertRow(ParseState *pstate, List *exprlist,
        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 -
@@ -787,19 +838,19 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 
        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);
 
@@ -827,14 +878,14 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
        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)
        {
@@ -933,7 +984,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
        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;
@@ -1044,8 +1095,8 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
        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");
@@ -1153,6 +1204,13 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 
        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.
@@ -1192,13 +1250,6 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
                                (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.
         */
@@ -1298,8 +1349,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
        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;
@@ -1498,8 +1549,8 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
                                                                                         &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.
                 */
@@ -1556,25 +1607,25 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
                                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);
 
@@ -1651,8 +1702,8 @@ determineRecursiveColTypes(ParseState *pstate, Node *larg, List *lcolinfo)
        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);
@@ -1993,29 +2044,21 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
  * 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;
@@ -2107,12 +2150,13 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
                                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);