]> 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 d69c0f17864c62a582f89296f9b2f3d1eccdabee..a1ad3e20e5bf90dfbe46e1a6d969f09d6bb868d6 100644 (file)
 /*-------------------------------------------------------------------------
  *
  * analyze.c
- *       transform the parse tree into a query tree
+ *       transform the raw parse tree into a query tree
  *
- * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * For optimizable statements, we are careful to obtain a suitable lock on
+ * each referenced table, and other modules of the backend preserve or
+ * re-obtain these locks before depending on the results.  It is therefore
+ * okay to do significant semantic analysis of these statements.  For
+ * utility commands, no locks are obtained here (and if they were, we could
+ * not be sure we'd still have them at execution).  Hence the general rule
+ * for utility commands is to just dump them into a Query node untransformed.
+ * DECLARE CURSOR and EXPLAIN are exceptions because they contain
+ * optimizable statements.
+ *
+ *
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *     $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.333 2006/04/22 01:25:59 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/heapam.h"
-#include "catalog/heap.h"
-#include "catalog/index.h"
-#include "catalog/namespace.h"
-#include "catalog/pg_index.h"
+#include "access/sysattr.h"
 #include "catalog/pg_type.h"
-#include "commands/defrem.h"
-#include "commands/prepare.h"
-#include "miscadmin.h"
 #include "nodes/makefuncs.h"
-#include "optimizer/clauses.h"
+#include "nodes/nodeFuncs.h"
 #include "optimizer/var.h"
 #include "parser/analyze.h"
-#include "parser/gramparse.h"
-#include "parser/parsetree.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/parse_type.h"
-#include "parser/parse_expr.h"
+#include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
-#include "utils/acl.h"
-#include "utils/builtins.h"
-#include "utils/fmgroids.h"
-#include "utils/guc.h"
-#include "utils/lsyscache.h"
-#include "utils/relcache.h"
-#include "utils/syscache.h"
-
-
-/* State shared by transformCreateSchemaStmt and its subroutines */
-typedef struct
-{
-       const char *stmtType;           /* "CREATE SCHEMA" or "ALTER SCHEMA" */
-       char       *schemaname;         /* name of schema */
-       char       *authid;                     /* owner of schema */
-       List       *sequences;          /* CREATE SEQUENCE items */
-       List       *tables;                     /* CREATE TABLE items */
-       List       *views;                      /* CREATE VIEW items */
-       List       *indexes;            /* CREATE INDEX items */
-       List       *triggers;           /* CREATE TRIGGER items */
-       List       *grants;                     /* GRANT items */
-       List       *fwconstraints;      /* Forward referencing FOREIGN KEY constraints */
-       List       *alters;                     /* Generated ALTER items (from the above) */
-       List       *ixconstraints;      /* index-creating constraints */
-       List       *blist;                      /* "before list" of things to do before
-                                                                * creating the schema */
-       List       *alist;                      /* "after list" of things to do after creating
-                                                                * the schema */
-} CreateSchemaStmtContext;
-
-/* State shared by transformCreateStmt and its subroutines */
-typedef struct
-{
-       const char *stmtType;           /* "CREATE TABLE" or "ALTER TABLE" */
-       RangeVar   *relation;           /* relation to create */
-       List       *inhRelations;       /* relations to inherit from */
-       bool            hasoids;                /* does relation have an OID column? */
-       bool            isalter;                /* true if altering existing table */
-       List       *columns;            /* ColumnDef items */
-       List       *ckconstraints;      /* CHECK constraints */
-       List       *fkconstraints;      /* FOREIGN KEY constraints */
-       List       *ixconstraints;      /* index-creating constraints */
-       List       *blist;                      /* "before list" of things to do before
-                                                                * creating the table */
-       List       *alist;                      /* "after list" of things to do after creating
-                                                                * the table */
-       IndexStmt  *pkey;                       /* PRIMARY KEY index, if any */
-} CreateStmtContext;
-
-typedef struct
-{
-       Oid                *paramTypes;
-       int                     numParams;
-} check_parameter_resolution_context;
+#include "utils/rel.h"
 
 
-static List *do_parse_analyze(Node *parseTree, ParseState *pstate);
-static Query *transformStmt(ParseState *pstate, Node *stmt,
-                         List **extras_before, List **extras_after);
-static Query *transformViewStmt(ParseState *pstate, ViewStmt *stmt,
-                                 List **extras_before, List **extras_after);
 static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
-static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
-                                       List **extras_before, List **extras_after);
-static Query *transformIndexStmt(ParseState *pstate, IndexStmt *stmt);
-static Query *transformRuleStmt(ParseState *query, RuleStmt *stmt,
-                                 List **extras_before, List **extras_after);
+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 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 *transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt);
-static Query *transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt);
-static Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt,
-                                       List **extras_before, List **extras_after);
-static Query *transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt,
-                                               List **extras_before, List **extras_after);
-static void transformColumnDefinition(ParseState *pstate,
-                                                 CreateStmtContext *cxt,
-                                                 ColumnDef *column);
-static void transformTableConstraint(ParseState *pstate,
-                                                CreateStmtContext *cxt,
-                                                Constraint *constraint);
-static void transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
-                                        InhRelation *inhrelation);
-static void transformIndexConstraints(ParseState *pstate,
-                                                 CreateStmtContext *cxt);
-static void transformFKConstraints(ParseState *pstate,
-                                          CreateStmtContext *cxt,
-                                          bool skipValidation,
-                                          bool isAddConstraint);
-static void applyColumnNames(List *dst, List *src);
-static List *getSetColTypes(ParseState *pstate, Node *node);
-static void transformLockingClause(Query *qry, LockingClause *lc);
-static void transformConstraintAttrs(List *constraintList);
-static void transformColumnType(ParseState *pstate, ColumnDef *column);
-static void release_pstate_resources(ParseState *pstate);
-static FromExpr *makeFromExpr(List *fromlist, Node *quals);
-static bool check_parameter_resolution_walker(Node *node,
-                                                               check_parameter_resolution_context *context);
+static Query *transformExplainStmt(ParseState *pstate,
+                                        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 List of Query nodes (we need a list since some commands
- * produce multiple Queries).  Optimizable statements require considerable
- * transformation, while many utility-type statements are simply hung off
+ * 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.
  */
-List *
+Query *
 parse_analyze(Node *parseTree, const char *sourceText,
                          Oid *paramTypes, int numParams)
 {
        ParseState *pstate = make_parsestate(NULL);
-       List       *result;
+       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;
 
-       result = do_parse_analyze(parseTree, pstate);
+       if (numParams > 0)
+               parse_fixed_parameters(pstate, paramTypes, numParams);
 
-       pfree(pstate);
+       query = transformStmt(pstate, parseTree);
 
-       return result;
+       free_parsestate(pstate);
+
+       return query;
 }
 
 /*
@@ -183,183 +105,67 @@ parse_analyze(Node *parseTree, const char *sourceText,
  * symbol datatypes from context.  The passed-in paramTypes[] array can
  * be modified or enlarged (via repalloc).
  */
-List *
+Query *
 parse_analyze_varparams(Node *parseTree, const char *sourceText,
                                                Oid **paramTypes, int *numParams)
 {
        ParseState *pstate = make_parsestate(NULL);
-       List       *result;
+       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 */
 
-       result = do_parse_analyze(parseTree, pstate);
+       pstate->p_sourcetext = sourceText;
 
-       *paramTypes = pstate->p_paramtypes;
-       *numParams = pstate->p_numparams;
+       parse_variable_parameters(pstate, paramTypes, numParams);
 
-       pfree(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 *) result, &context);
-       }
+       free_parsestate(pstate);
 
-       return result;
+       return query;
 }
 
 /*
  * parse_sub_analyze
  *             Entry point for recursively analyzing a sub-statement.
  */
-List *
-parse_sub_analyze(Node *parseTree, ParseState *parentParseState)
+Query *
+parse_sub_analyze(Node *parseTree, ParseState *parentParseState,
+                                 CommonTableExpr *parentCTE,
+                                 bool locked_from_parent)
 {
        ParseState *pstate = make_parsestate(parentParseState);
-       List       *result;
-
-       result = do_parse_analyze(parseTree, pstate);
-
-       pfree(pstate);
-
-       return result;
-}
-
-/*
- * do_parse_analyze
- *             Workhorse code shared by the above variants of parse_analyze.
- */
-static List *
-do_parse_analyze(Node *parseTree, ParseState *pstate)
-{
-       List       *result = NIL;
-
-       /* Lists to return extra commands from transformation */
-       List       *extras_before = NIL;
-       List       *extras_after = NIL;
        Query      *query;
-       ListCell   *l;
-
-       query = transformStmt(pstate, parseTree, &extras_before, &extras_after);
-
-       /* don't need to access result relation any more */
-       release_pstate_resources(pstate);
-
-       foreach(l, extras_before)
-               result = list_concat(result, parse_sub_analyze(lfirst(l), pstate));
 
-       result = lappend(result, query);
+       pstate->p_parent_cte = parentCTE;
+       pstate->p_locked_from_parent = locked_from_parent;
 
-       foreach(l, extras_after)
-               result = list_concat(result, parse_sub_analyze(lfirst(l), pstate));
-
-       /*
-        * Make sure that only the original query is marked original. We have to
-        * do this explicitly since recursive calls of do_parse_analyze will have
-        * marked some of the added-on queries as "original".  Also mark only the
-        * original query as allowed to set the command-result tag.
-        */
-       foreach(l, result)
-       {
-               Query      *q = lfirst(l);
-
-               if (q == query)
-               {
-                       q->querySource = QSRC_ORIGINAL;
-                       q->canSetTag = true;
-               }
-               else
-               {
-                       q->querySource = QSRC_PARSER;
-                       q->canSetTag = false;
-               }
-       }
+       query = transformStmt(pstate, parseTree);
 
-       return result;
-}
+       free_parsestate(pstate);
 
-static void
-release_pstate_resources(ParseState *pstate)
-{
-       if (pstate->p_target_relation != NULL)
-               heap_close(pstate->p_target_relation, NoLock);
-       pstate->p_target_relation = NULL;
-       pstate->p_target_rangetblentry = NULL;
+       return query;
 }
 
 /*
  * transformStmt -
  *       transform a Parse tree into a Query tree.
  */
-static Query *
-transformStmt(ParseState *pstate, Node *parseTree,
-                         List **extras_before, List **extras_after)
+Query *
+transformStmt(ParseState *pstate, Node *parseTree)
 {
-       Query      *result = NULL;
+       Query      *result;
 
        switch (nodeTag(parseTree))
        {
-                       /*
-                        * Non-optimizable statements
-                        */
-               case T_CreateStmt:
-                       result = transformCreateStmt(pstate, (CreateStmt *) parseTree,
-                                                                                extras_before, extras_after);
-                       break;
-
-               case T_IndexStmt:
-                       result = transformIndexStmt(pstate, (IndexStmt *) parseTree);
-                       break;
-
-               case T_RuleStmt:
-                       result = transformRuleStmt(pstate, (RuleStmt *) parseTree,
-                                                                          extras_before, extras_after);
-                       break;
-
-               case T_ViewStmt:
-                       result = transformViewStmt(pstate, (ViewStmt *) parseTree,
-                                                                          extras_before, extras_after);
-                       break;
-
-               case T_ExplainStmt:
-                       {
-                               ExplainStmt *n = (ExplainStmt *) parseTree;
-
-                               result = makeNode(Query);
-                               result->commandType = CMD_UTILITY;
-                               n->query = transformStmt(pstate, (Node *) n->query,
-                                                                                extras_before, extras_after);
-                               result->utilityStmt = (Node *) parseTree;
-                       }
-                       break;
-
-               case T_AlterTableStmt:
-                       result = transformAlterTableStmt(pstate,
-                                                                                        (AlterTableStmt *) parseTree,
-                                                                                        extras_before, extras_after);
-                       break;
-
-               case T_PrepareStmt:
-                       result = transformPrepareStmt(pstate, (PrepareStmt *) parseTree);
-                       break;
-
-               case T_ExecuteStmt:
-                       result = transformExecuteStmt(pstate, (ExecuteStmt *) parseTree);
-                       break;
-
                        /*
                         * Optimizable statements
                         */
                case T_InsertStmt:
-                       result = transformInsertStmt(pstate, (InsertStmt *) parseTree,
-                                                                                extras_before, extras_after);
+                       result = transformInsertStmt(pstate, (InsertStmt *) parseTree);
                        break;
 
                case T_DeleteStmt:
@@ -371,24 +177,36 @@ transformStmt(ParseState *pstate, Node *parseTree,
                        break;
 
                case T_SelectStmt:
-                       if (((SelectStmt *) parseTree)->op == SETOP_NONE)
-                               result = transformSelectStmt(pstate,
-                                                                                        (SelectStmt *) parseTree);
-                       else
-                               result = transformSetOperationStmt(pstate,
-                                                                                                  (SelectStmt *) parseTree);
+                       {
+                               SelectStmt *n = (SelectStmt *) parseTree;
+
+                               if (n->valuesLists)
+                                       result = transformValuesClause(pstate, n);
+                               else if (n->op == SETOP_NONE)
+                                       result = transformSelectStmt(pstate, n);
+                               else
+                                       result = transformSetOperationStmt(pstate, n);
+                       }
                        break;
 
+                       /*
+                        * Special cases
+                        */
                case T_DeclareCursorStmt:
                        result = transformDeclareCursorStmt(pstate,
                                                                                        (DeclareCursorStmt *) parseTree);
                        break;
 
+               case T_ExplainStmt:
+                       result = transformExplainStmt(pstate,
+                                                                                 (ExplainStmt *) parseTree);
+                       break;
+
                default:
 
                        /*
-                        * other statements don't require any transformation-- just return
-                        * the original parsetree, yea!
+                        * other statements don't require any transformation; just return
+                        * the original parsetree with a Query node plastered on top.
                         */
                        result = makeNode(Query);
                        result->commandType = CMD_UTILITY;
@@ -400,63 +218,54 @@ transformStmt(ParseState *pstate, Node *parseTree,
        result->querySource = QSRC_ORIGINAL;
        result->canSetTag = true;
 
-       /*
-        * Check that we did not produce too many resnos; at the very least we
-        * cannot allow more than 2^16, since that would exceed the range of a
-        * AttrNumber. It seems safest to use MaxTupleAttributeNumber.
-        */
-       if (pstate->p_next_resno - 1 > MaxTupleAttributeNumber)
-               ereport(ERROR,
-                               (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-                                errmsg("target lists can have at most %d entries",
-                                               MaxTupleAttributeNumber)));
-
        return result;
 }
 
-static Query *
-transformViewStmt(ParseState *pstate, ViewStmt *stmt,
-                                 List **extras_before, List **extras_after)
+/*
+ * 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)
 {
-       Query      *result = makeNode(Query);
+       bool            result;
 
-       result->commandType = CMD_UTILITY;
-       result->utilityStmt = (Node *) stmt;
-
-       stmt->query = transformStmt(pstate, (Node *) stmt->query,
-                                                               extras_before, extras_after);
+       if (parseTree == NULL)
+               return false;
 
-       /*
-        * If a list of column names was given, run through and insert these into
-        * the actual query tree. - thomas 2000-03-08
-        *
-        * Outer loop is over targetlist to make it easier to skip junk targetlist
-        * entries.
-        */
-       if (stmt->aliases != NIL)
+       switch (nodeTag(parseTree))
        {
-               ListCell   *alist_item = list_head(stmt->aliases);
-               ListCell   *targetList;
+                       /*
+                        * Optimizable statements
+                        */
+               case T_InsertStmt:
+               case T_DeleteStmt:
+               case T_UpdateStmt:
+               case T_SelectStmt:
+                       result = true;
+                       break;
 
-               foreach(targetList, stmt->query->targetList)
-               {
-                       TargetEntry *te = (TargetEntry *) lfirst(targetList);
+                       /*
+                        * Special cases
+                        */
+               case T_DeclareCursorStmt:
+                       /* yes, because it's analyzed just like SELECT */
+                       result = true;
+                       break;
 
-                       Assert(IsA(te, TargetEntry));
-                       /* junk columns don't get aliases */
-                       if (te->resjunk)
-                               continue;
-                       te->resname = pstrdup(strVal(lfirst(alist_item)));
-                       alist_item = lnext(alist_item);
-                       if (alist_item == NULL)
-                               break;                  /* done assigning aliases */
-               }
+               case T_ExplainStmt:
+                       /* yes, because we must analyze the contained statement */
+                       result = true;
+                       break;
 
-               if (alist_item != NULL)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_SYNTAX_ERROR),
-                                        errmsg("CREATE VIEW specifies more column "
-                                                       "names than columns")));
+               default:
+                       /* other utility statements don't have any real parse analysis */
+                       result = false;
+                       break;
        }
 
        return result;
@@ -490,9 +299,10 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
         */
        transformFromClause(pstate, stmt->usingClause);
 
-       /* fix where clause */
        qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
 
+       qry->returningList = transformReturningList(pstate, stmt->returningList);
+
        /* done building the range table and jointree */
        qry->rtable = pstate->p_rtable;
        qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
@@ -501,6 +311,9 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
        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;
 }
@@ -510,23 +323,33 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
  *       transform an Insert Statement
  */
 static Query *
-transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
-                                       List **extras_before, List **extras_after)
+transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 {
        Query      *qry = makeNode(Query);
-       Query      *selectQuery = NULL;
+       SelectStmt *selectStmt = (SelectStmt *) stmt->selectStmt;
+       List       *exprList = NIL;
+       bool            isGeneralSelect;
        List       *sub_rtable;
        List       *sub_relnamespace;
        List       *sub_varnamespace;
        List       *icolumns;
        List       *attrnos;
+       RangeTblEntry *rte;
+       RangeTblRef *rtr;
        ListCell   *icols;
        ListCell   *attnos;
-       ListCell   *tl;
+       ListCell   *lc;
 
        qry->commandType = CMD_INSERT;
        pstate->p_is_insert = true;
 
+       /*
+        * We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL),
+        * VALUES list, or general SELECT input.  We special-case VALUES, both for
+        * efficiency and so we can handle DEFAULT specifications.
+        */
+       isGeneralSelect = (selectStmt && selectStmt->valuesLists == NIL);
+
        /*
         * If a non-nil rangetable/namespace was passed in, and we are doing
         * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
@@ -536,7 +359,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
         * The SELECT's joinlist is not affected however.  We must do this before
         * adding the target table to the INSERT's rtable.
         */
-       if (stmt->selectStmt)
+       if (isGeneralSelect)
        {
                sub_rtable = pstate->p_rtable;
                pstate->p_rtable = NIL;
@@ -544,6 +367,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
                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
        {
@@ -561,10 +386,23 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
        qry->resultRelation = setTargetTable(pstate, stmt->relation,
                                                                                 false, false, ACL_INSERT);
 
+       /* Validate stmt->cols list, or build default list if no list given */
+       icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos);
+       Assert(list_length(icolumns) == list_length(attrnos));
+
        /*
-        * Is it INSERT ... SELECT or INSERT ... VALUES?
+        * Determine which variant of INSERT we have.
         */
-       if (stmt->selectStmt)
+       if (selectStmt == NULL)
+       {
+               /*
+                * We have INSERT ... DEFAULT VALUES.  We can handle this case by
+                * emitting an empty targetlist --- all columns will be defaulted when
+                * the planner expands the targetlist.
+                */
+               exprList = NIL;
+       }
+       else if (isGeneralSelect)
        {
                /*
                 * We make the sub-pstate a child of the outer pstate so that it can
@@ -574,8 +412,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
                 * see.
                 */
                ParseState *sub_pstate = make_parsestate(pstate);
-               RangeTblEntry *rte;
-               RangeTblRef *rtr;
+               Query      *selectQuery;
 
                /*
                 * Process the source SELECT.
@@ -586,25 +423,25 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
                 * 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;
 
-               /*
-                * Note: we are not expecting that extras_before and extras_after are
-                * going to be used by the transformation of the SELECT statement.
-                */
-               selectQuery = transformStmt(sub_pstate, stmt->selectStmt,
-                                                                       extras_before, extras_after);
+               selectQuery = transformStmt(sub_pstate, stmt->selectStmt);
 
-               release_pstate_resources(sub_pstate);
-               pfree(sub_pstate);
+               free_parsestate(sub_pstate);
 
-               Assert(IsA(selectQuery, Query));
-               Assert(selectQuery->commandType == CMD_SELECT);
-               if (selectQuery->into)
+               /* The grammar should have produced a SELECT, but it might have INTO */
+               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 may not 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
@@ -621,8 +458,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
                pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
 
                /*----------
-                * Generate a targetlist for the INSERT that selects all the
-                * non-resjunk columns from the subquery.  (We need this to be
+                * Generate an expression list for the INSERT that selects all the
+                * non-resjunk columns from the subquery.  (INSERT's tlist must be
                 * separate from the subquery's tlist because we may add columns,
                 * insert datatype coercions, etc.)
                 *
@@ -634,10 +471,10 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
                 *              INSERT INTO foo SELECT 'bar', ... FROM baz
                 *----------
                 */
-               qry->targetList = NIL;
-               foreach(tl, selectQuery->targetList)
+               exprList = NIL;
+               foreach(lc, selectQuery->targetList)
                {
-                       TargetEntry *tle = (TargetEntry *) lfirst(tl);
+                       TargetEntry *tle = (TargetEntry *) lfirst(lc);
                        Expr       *expr;
 
                        if (tle->resjunk)
@@ -647,1237 +484,689 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
                                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);
-                       tle = makeTargetEntry(expr,
-                                                                 (AttrNumber) pstate->p_next_resno++,
-                                                                 tle->resname,
-                                                                 false);
-                       qry->targetList = lappend(qry->targetList, tle);
+                       {
+                               Var                *var = makeVarFromTargetEntry(rtr->rtindex, tle);
+
+                               var->location = exprLocation((Node *) tle->expr);
+                               expr = (Expr *) var;
+                       }
+                       exprList = lappend(exprList, expr);
                }
+
+               /* Prepare row for assignment to target table */
+               exprList = transformInsertRow(pstate, exprList,
+                                                                         stmt->cols,
+                                                                         icolumns, attrnos);
        }
-       else
+       else if (list_length(selectStmt->valuesLists) > 1)
        {
                /*
-                * For INSERT ... VALUES, transform the given list of values to form a
-                * targetlist for the INSERT.
+                * Process INSERT ... VALUES with multiple VALUES sublists. We
+                * generate a VALUES RTE holding the transformed expression lists, and
+                * build up a targetlist containing Vars that reference the VALUES
+                * RTE.
+                */
+               List       *exprsLists = NIL;
+               int                     sublist_length = -1;
+
+               /* 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);
+
+                       /* Do basic expression transformation (same as a ROW() expr) */
+                       sublist = transformExpressionList(pstate, sublist);
+
+                       /*
+                        * All the sublists must be the same length, *after*
+                        * transformation (which might expand '*' into multiple items).
+                        * The VALUES RTE can't handle anything different.
+                        */
+                       if (sublist_length < 0)
+                       {
+                               /* Remember post-transformation length of first sublist */
+                               sublist_length = list_length(sublist);
+                       }
+                       else if (sublist_length != list_length(sublist))
+                       {
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("VALUES lists must all be the same length"),
+                                                parser_errposition(pstate,
+                                                                                       exprLocation((Node *) sublist))));
+                       }
+
+                       /* Prepare row for assignment to target table */
+                       sublist = transformInsertRow(pstate, sublist,
+                                                                                stmt->cols,
+                                                                                icolumns, attrnos);
+
+                       exprsLists = lappend(exprsLists, sublist);
+               }
+
+               /*
+                * There mustn't have been any table references in the expressions,
+                * else strange things would happen, like Cartesian products of those
+                * tables with the VALUES list ...
+                */
+               if (pstate->p_joinlist != NIL)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("VALUES must not contain table references"),
+                                        parser_errposition(pstate,
+                                                         locate_var_of_level((Node *) exprsLists, 0))));
+
+               /*
+                * Another thing we can't currently support is NEW/OLD references in
+                * rules --- seems we'd need something like SQL99's LATERAL construct
+                * to ensure that the values would be available while evaluating the
+                * VALUES RTE.  This is a shame.  FIXME
+                */
+               if (list_length(pstate->p_rtable) != 1 &&
+                       contain_vars_of_level((Node *) exprsLists, 0))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("VALUES must not contain OLD or NEW references"),
+                                        errhint("Use SELECT ... UNION ALL ... instead."),
+                                        parser_errposition(pstate,
+                                                         locate_var_of_level((Node *) exprsLists, 0))));
+
+               /*
+                * Generate the VALUES RTE
+                */
+               rte = addRangeTableEntryForValues(pstate, exprsLists, NULL, true);
+               rtr = makeNode(RangeTblRef);
+               /* assume new rte is at end */
+               rtr->rtindex = list_length(pstate->p_rtable);
+               Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
+               pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
+
+               /*
+                * Generate list of Vars referencing the RTE
                 */
-               qry->targetList = transformTargetList(pstate, stmt->targetList);
+               expandRTE(rte, rtr->rtindex, 0, -1, false, NULL, &exprList);
        }
+       else
+       {
+               /*----------
+                * Process INSERT ... VALUES with a single VALUES sublist.
+                * We treat this separately for efficiency and for historical
+                * compatibility --- specifically, allowing table references,
+                * such as
+                *                      INSERT INTO foo VALUES(bar.*)
+                *
+                * The sublist is just computed directly as the Query's targetlist,
+                * with no VALUES RTE.  So it works just like SELECT without FROM.
+                *----------
+                */
+               List       *valuesLists = selectStmt->valuesLists;
 
-       /*
-        * Now we are done with SELECT-like processing, and can get on with
-        * transforming the target list to match the INSERT target columns.
-        */
+               Assert(list_length(valuesLists) == 1);
 
-       /* Prepare to assign non-conflicting resnos to resjunk attributes */
-       if (pstate->p_next_resno <= pstate->p_target_relation->rd_rel->relnatts)
-               pstate->p_next_resno = pstate->p_target_relation->rd_rel->relnatts + 1;
+               /* process the WITH clause */
+               if (selectStmt->withClause)
+               {
+                       qry->hasRecursive = selectStmt->withClause->recursive;
+                       qry->cteList = transformWithClause(pstate, selectStmt->withClause);
+               }
 
-       /* Validate stmt->cols list, or build default list if no list given */
-       icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos);
+               /* Do basic expression transformation (same as a ROW() expr) */
+               exprList = transformExpressionList(pstate,
+                                                                                  (List *) linitial(valuesLists));
+
+               /* Prepare row for assignment to target table */
+               exprList = transformInsertRow(pstate, exprList,
+                                                                         stmt->cols,
+                                                                         icolumns, attrnos);
+       }
 
        /*
-        * Prepare columns for assignment to target table.
+        * 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);
-       foreach(tl, qry->targetList)
+       foreach(lc, exprList)
        {
-               TargetEntry *tle = (TargetEntry *) lfirst(tl);
+               Expr       *expr = (Expr *) lfirst(lc);
                ResTarget  *col;
-
-               if (icols == NULL || attnos == NULL)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_SYNTAX_ERROR),
-                                errmsg("INSERT has more expressions than target columns")));
+               AttrNumber      attr_num;
+               TargetEntry *tle;
 
                col = (ResTarget *) lfirst(icols);
                Assert(IsA(col, ResTarget));
+               attr_num = (AttrNumber) lfirst_int(attnos);
+
+               tle = makeTargetEntry(expr,
+                                                         attr_num,
+                                                         col->name,
+                                                         false);
+               qry->targetList = lappend(qry->targetList, tle);
 
-               Assert(!tle->resjunk);
-               updateTargetListEntry(pstate, tle, col->name, lfirst_int(attnos),
-                                                         col->indirection, col->location);
+               rte->modifiedCols = bms_add_member(rte->modifiedCols,
+                                                         attr_num - FirstLowInvalidHeapAttributeNumber);
 
                icols = lnext(icols);
                attnos = lnext(attnos);
        }
 
        /*
-        * Ensure that the targetlist has the same number of entries that were
-        * present in the columns list.  Don't do the check unless an explicit
-        * columns list was given, though.
+        * If we have a RETURNING clause, we need to add the target relation to
+        * the query namespace before processing it, so that Var references in
+        * RETURNING will work.  Also, remove any namespace entries added in a
+        * sub-SELECT or VALUES list.
         */
-       if (stmt->cols != NIL && (icols != NULL || attnos != NULL))
-               ereport(ERROR,
-                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                errmsg("INSERT has more target columns than expressions")));
+       if (stmt->returningList)
+       {
+               pstate->p_relnamespace = NIL;
+               pstate->p_varnamespace = NIL;
+               addRTEtoQuery(pstate, pstate->p_target_rangetblentry,
+                                         false, true, true);
+               qry->returningList = transformReturningList(pstate,
+                                                                                                       stmt->returningList);
+       }
 
        /* done building the range table and jointree */
        qry->rtable = pstate->p_rtable;
        qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
        qry->hasSubLinks = pstate->p_hasSubLinks;
-       qry->hasAggs = pstate->p_hasAggs;
+       /* aggregates not allowed (but subselects are okay) */
        if (pstate->p_hasAggs)
-               parseCheckAggregates(pstate, qry);
+               ereport(ERROR,
+                               (errcode(ERRCODE_GROUPING_ERROR),
+                                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;
 }
 
 /*
- * transformCreateStmt -
- *       transforms the "create table" statement
- *       SQL92 allows constraints to be scattered all over, so thumb through
- *        the columns and collect all constraints into one place.
- *       If there are any implied indices (e.g. UNIQUE or PRIMARY KEY)
- *        then expand those into multiple IndexStmt blocks.
- *       - thomas 1997-12-02
+ * Prepare an INSERT row for assignment to the target table.
+ *
+ * The row might be either a VALUES row, or variables referencing a
+ * sub-SELECT output.
  */
-static Query *
-transformCreateStmt(ParseState *pstate, CreateStmt *stmt,
-                                       List **extras_before, List **extras_after)
+static List *
+transformInsertRow(ParseState *pstate, List *exprlist,
+                                  List *stmtcols, List *icolumns, List *attrnos)
 {
-       CreateStmtContext cxt;
-       Query      *q;
-       ListCell   *elements;
-
-       cxt.stmtType = "CREATE TABLE";
-       cxt.relation = stmt->relation;
-       cxt.inhRelations = stmt->inhRelations;
-       cxt.isalter = false;
-       cxt.columns = NIL;
-       cxt.ckconstraints = NIL;
-       cxt.fkconstraints = NIL;
-       cxt.ixconstraints = NIL;
-       cxt.blist = NIL;
-       cxt.alist = NIL;
-       cxt.pkey = NULL;
-       cxt.hasoids = interpretOidsOption(stmt->hasoids);
+       List       *result;
+       ListCell   *lc;
+       ListCell   *icols;
+       ListCell   *attnos;
 
        /*
-        * Run through each primary element in the table creation clause. Separate
-        * column defs from constraints, and do preliminary analysis.
+        * Check length of expr list.  It must not have more expressions than
+        * there are target columns.  We allow fewer, but only if no explicit
+        * columns list was given (the remaining columns are implicitly
+        * defaulted).  Note we must check this *after* transformation because
+        * that could expand '*' into multiple items.
         */
-       foreach(elements, stmt->tableElts)
+       if (list_length(exprlist) > list_length(icolumns))
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                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))
        {
-               Node       *element = lfirst(elements);
-
-               switch (nodeTag(element))
-               {
-                       case T_ColumnDef:
-                               transformColumnDefinition(pstate, &cxt,
-                                                                                 (ColumnDef *) element);
-                               break;
-
-                       case T_Constraint:
-                               transformTableConstraint(pstate, &cxt,
-                                                                                (Constraint *) element);
-                               break;
-
-                       case T_FkConstraint:
-                               /* No pre-transformation needed */
-                               cxt.fkconstraints = lappend(cxt.fkconstraints, element);
-                               break;
-
-                       case T_InhRelation:
-                               transformInhRelation(pstate, &cxt,
-                                                                        (InhRelation *) element);
-                               break;
-
-                       default:
-                               elog(ERROR, "unrecognized node type: %d",
-                                        (int) nodeTag(element));
-                               break;
-               }
+               /*
+                * 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))))));
        }
 
-       Assert(stmt->constraints == NIL);
-
        /*
-        * Postprocess constraints that give rise to index definitions.
+        * Prepare columns for assignment to target table.
         */
-       transformIndexConstraints(pstate, &cxt);
-
-       /*
-        * Postprocess foreign-key constraints.
-        */
-       transformFKConstraints(pstate, &cxt, true, false);
-
-       /*
-        * Output results.
-        */
-       q = makeNode(Query);
-       q->commandType = CMD_UTILITY;
-       q->utilityStmt = (Node *) stmt;
-       stmt->tableElts = cxt.columns;
-       stmt->constraints = cxt.ckconstraints;
-       *extras_before = list_concat(*extras_before, cxt.blist);
-       *extras_after = list_concat(cxt.alist, *extras_after);
-
-       return q;
-}
-
-static void
-transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt,
-                                                 ColumnDef *column)
-{
-       bool            is_serial;
-       bool            saw_nullable;
-       Constraint *constraint;
-       ListCell   *clist;
-
-       cxt->columns = lappend(cxt->columns, column);
-
-       /* Check for SERIAL pseudo-types */
-       is_serial = false;
-       if (list_length(column->typename->names) == 1)
-       {
-               char       *typname = strVal(linitial(column->typename->names));
-
-               if (strcmp(typname, "serial") == 0 ||
-                       strcmp(typname, "serial4") == 0)
-               {
-                       is_serial = true;
-                       column->typename->names = NIL;
-                       column->typename->typeid = INT4OID;
-               }
-               else if (strcmp(typname, "bigserial") == 0 ||
-                                strcmp(typname, "serial8") == 0)
-               {
-                       is_serial = true;
-                       column->typename->names = NIL;
-                       column->typename->typeid = INT8OID;
-               }
-       }
-
-       /* Do necessary work on the column type declaration */
-       transformColumnType(pstate, column);
-
-       /* Special actions for SERIAL pseudo-types */
-       if (is_serial)
+       result = NIL;
+       icols = list_head(icolumns);
+       attnos = list_head(attrnos);
+       foreach(lc, exprlist)
        {
-               Oid                     snamespaceid;
-               char       *snamespace;
-               char       *sname;
-               char       *qstring;
-               A_Const    *snamenode;
-               FuncCall   *funccallnode;
-               CreateSeqStmt *seqstmt;
-
-               /*
-                * Determine namespace and name to use for the sequence.
-                *
-                * Although we use ChooseRelationName, it's not guaranteed that the
-                * selected sequence name won't conflict; given sufficiently long
-                * field names, two different serial columns in the same table could
-                * be assigned the same sequence name, and we'd not notice since we
-                * aren't creating the sequence quite yet.  In practice this seems
-                * quite unlikely to be a problem, especially since few people would
-                * need two serial columns in one table.
-                */
-               snamespaceid = RangeVarGetCreationNamespace(cxt->relation);
-               snamespace = get_namespace_name(snamespaceid);
-               sname = ChooseRelationName(cxt->relation->relname,
-                                                                  column->colname,
-                                                                  "seq",
-                                                                  snamespaceid);
-
-               ereport(NOTICE,
-                               (errmsg("%s will create implicit sequence \"%s\" for serial column \"%s.%s\"",
-                                               cxt->stmtType, sname,
-                                               cxt->relation->relname, column->colname)));
+               Expr       *expr = (Expr *) lfirst(lc);
+               ResTarget  *col;
 
-               /*
-                * Build a CREATE SEQUENCE command to create the sequence object, and
-                * add it to the list of things to be done before this CREATE/ALTER
-                * TABLE.
-                */
-               seqstmt = makeNode(CreateSeqStmt);
-               seqstmt->sequence = makeRangeVar(snamespace, sname);
-               seqstmt->options = NIL;
+               col = (ResTarget *) lfirst(icols);
+               Assert(IsA(col, ResTarget));
 
-               cxt->blist = lappend(cxt->blist, seqstmt);
+               expr = transformAssignedExpr(pstate, expr,
+                                                                        col->name,
+                                                                        lfirst_int(attnos),
+                                                                        col->indirection,
+                                                                        col->location);
 
-               /*
-                * Mark the ColumnDef so that during execution, an appropriate
-                * dependency will be added from the sequence to the column.
-                */
-               column->support = makeRangeVar(snamespace, sname);
+               result = lappend(result, expr);
 
-               /*
-                * Create appropriate constraints for SERIAL.  We do this in full,
-                * rather than shortcutting, so that we will detect any conflicting
-                * constraints the user wrote (like a different DEFAULT).
-                *
-                * Create an expression tree representing the function call
-                * nextval('sequencename').  We cannot reduce the raw tree to cooked
-                * form until after the sequence is created, but there's no need to do
-                * so.
-                */
-               qstring = quote_qualified_identifier(snamespace, sname);
-               snamenode = makeNode(A_Const);
-               snamenode->val.type = T_String;
-               snamenode->val.val.str = qstring;
-               snamenode->typename = SystemTypeName("regclass");
-               funccallnode = makeNode(FuncCall);
-               funccallnode->funcname = SystemFuncName("nextval");
-               funccallnode->args = list_make1(snamenode);
-               funccallnode->agg_star = false;
-               funccallnode->agg_distinct = false;
-               funccallnode->location = -1;
-
-               constraint = makeNode(Constraint);
-               constraint->contype = CONSTR_DEFAULT;
-               constraint->raw_expr = (Node *) funccallnode;
-               constraint->cooked_expr = NULL;
-               constraint->keys = NIL;
-               column->constraints = lappend(column->constraints, constraint);
-
-               constraint = makeNode(Constraint);
-               constraint->contype = CONSTR_NOTNULL;
-               column->constraints = lappend(column->constraints, constraint);
+               icols = lnext(icols);
+               attnos = lnext(attnos);
        }
 
-       /* Process column constraints, if any... */
-       transformConstraintAttrs(column->constraints);
-
-       saw_nullable = false;
+       return result;
+}
 
-       foreach(clist, column->constraints)
+/*
+ * 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))
        {
-               constraint = lfirst(clist);
+               Var                *var = (Var *) expr;
+               AttrNumber      attnum = var->varattno;
 
-               /*
-                * If this column constraint is a FOREIGN KEY constraint, then we fill
-                * in the current attribute's name and throw it into the list of FK
-                * constraints to be processed later.
-                */
-               if (IsA(constraint, FkConstraint))
+               if (attnum > 0 && var->vartype == RECORDOID)
                {
-                       FkConstraint *fkconstraint = (FkConstraint *) constraint;
-
-                       fkconstraint->fk_attrs = list_make1(makeString(column->colname));
-                       cxt->fkconstraints = lappend(cxt->fkconstraints, fkconstraint);
-                       continue;
-               }
-
-               Assert(IsA(constraint, Constraint));
-
-               switch (constraint->contype)
-               {
-                       case CONSTR_NULL:
-                               if (saw_nullable && column->is_not_null)
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_SYNTAX_ERROR),
-                                                        errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
-                                                                 column->colname, cxt->relation->relname)));
-                               column->is_not_null = FALSE;
-                               saw_nullable = true;
-                               break;
-
-                       case CONSTR_NOTNULL:
-                               if (saw_nullable && !column->is_not_null)
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_SYNTAX_ERROR),
-                                                        errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
-                                                                 column->colname, cxt->relation->relname)));
-                               column->is_not_null = TRUE;
-                               saw_nullable = true;
-                               break;
-
-                       case CONSTR_DEFAULT:
-                               if (column->raw_default != NULL)
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_SYNTAX_ERROR),
-                                                        errmsg("multiple default values specified for column \"%s\" of table \"%s\"",
-                                                                 column->colname, cxt->relation->relname)));
-                               column->raw_default = constraint->raw_expr;
-                               Assert(constraint->cooked_expr == NULL);
-                               break;
+                       RangeTblEntry *rte;
 
-                       case CONSTR_PRIMARY:
-                       case CONSTR_UNIQUE:
-                               if (constraint->keys == NIL)
-                                       constraint->keys = list_make1(makeString(column->colname));
-                               cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
-                               break;
-
-                       case CONSTR_CHECK:
-                               cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
-                               break;
-
-                       case CONSTR_ATTR_DEFERRABLE:
-                       case CONSTR_ATTR_NOT_DEFERRABLE:
-                       case CONSTR_ATTR_DEFERRED:
-                       case CONSTR_ATTR_IMMEDIATE:
-                               /* transformConstraintAttrs took care of these */
-                               break;
-
-                       default:
-                               elog(ERROR, "unrecognized constraint type: %d",
-                                        constraint->contype);
-                               break;
+                       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;
 }
 
-static void
-transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
-                                                Constraint *constraint)
-{
-       switch (constraint->contype)
-       {
-               case CONSTR_PRIMARY:
-               case CONSTR_UNIQUE:
-                       cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
-                       break;
-
-               case CONSTR_CHECK:
-                       cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
-                       break;
-
-               case CONSTR_NULL:
-               case CONSTR_NOTNULL:
-               case CONSTR_DEFAULT:
-               case CONSTR_ATTR_DEFERRABLE:
-               case CONSTR_ATTR_NOT_DEFERRABLE:
-               case CONSTR_ATTR_DEFERRED:
-               case CONSTR_ATTR_IMMEDIATE:
-                       elog(ERROR, "invalid context for constraint type %d",
-                                constraint->contype);
-                       break;
-
-               default:
-                       elog(ERROR, "unrecognized constraint type: %d",
-                                constraint->contype);
-                       break;
-       }
-}
 
 /*
- * transformInhRelation
+ * transformSelectStmt -
+ *       transforms a Select Statement
  *
- * Change the LIKE <subtable> portion of a CREATE TABLE statement into the
- * column definitions which recreate the user defined column portions of <subtable>.
+ * Note: this covers only cases with no set operations and no VALUES lists;
+ * see below for the other cases.
  */
-static void
-transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
-                                        InhRelation *inhRelation)
+static Query *
+transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 {
-       AttrNumber      parent_attno;
-
-       Relation        relation;
-       TupleDesc       tupleDesc;
-       TupleConstr *constr;
-       AclResult       aclresult;
-
-       relation = heap_openrv(inhRelation->relation, AccessShareLock);
-
-       if (relation->rd_rel->relkind != RELKIND_RELATION)
-               ereport(ERROR,
-                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                                errmsg("inherited relation \"%s\" is not a table",
-                                               inhRelation->relation->relname)));
-
-       /*
-        * Check for SELECT privilages
-        */
-       aclresult = pg_class_aclcheck(RelationGetRelid(relation), GetUserId(),
-                                                                 ACL_SELECT);
-       if (aclresult != ACLCHECK_OK)
-               aclcheck_error(aclresult, ACL_KIND_CLASS,
-                                          RelationGetRelationName(relation));
+       Query      *qry = makeNode(Query);
+       Node       *qual;
+       ListCell   *l;
 
-       tupleDesc = RelationGetDescr(relation);
-       constr = tupleDesc->constr;
+       qry->commandType = CMD_SELECT;
 
-       /*
-        * Insert the inherited attributes into the cxt for the new table
-        * definition.
-        */
-       for (parent_attno = 1; parent_attno <= tupleDesc->natts;
-                parent_attno++)
+       /* process the WITH clause independently of all else */
+       if (stmt->withClause)
        {
-               Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
-               char       *attributeName = NameStr(attribute->attname);
-               ColumnDef  *def;
+               qry->hasRecursive = stmt->withClause->recursive;
+               qry->cteList = transformWithClause(pstate, stmt->withClause);
+       }
 
-               /*
-                * Ignore dropped columns in the parent.
-                */
-               if (attribute->attisdropped)
-                       continue;
+       /* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
+       pstate->p_locking_clause = stmt->lockingClause;
 
-               /*
-                * Create a new inherited column.
-                *
-                * For constraints, ONLY the NOT NULL constraint is inherited by the
-                * new column definition per SQL99.
-                */
-               def = makeNode(ColumnDef);
-               def->colname = pstrdup(attributeName);
-               def->typename = makeTypeNameFromOid(attribute->atttypid,
-                                                                                       attribute->atttypmod);
-               def->inhcount = 0;
-               def->is_local = false;
-               def->is_not_null = attribute->attnotnull;
-               def->raw_default = NULL;
-               def->cooked_default = NULL;
-               def->constraints = NIL;
-               def->support = NULL;
+       /* make WINDOW info available for window functions, too */
+       pstate->p_windowdefs = stmt->windowClause;
 
-               /*
-                * Add to column list
-                */
-               cxt->columns = lappend(cxt->columns, def);
+       /* process the FROM clause */
+       transformFromClause(pstate, stmt->fromClause);
 
-               /*
-                * Copy default if any, and the default has been requested
-                */
-               if (attribute->atthasdef && inhRelation->including_defaults)
-               {
-                       char       *this_default = NULL;
-                       AttrDefault *attrdef;
-                       int                     i;
-
-                       /* Find default in constraint structure */
-                       Assert(constr != NULL);
-                       attrdef = constr->defval;
-                       for (i = 0; i < constr->num_defval; i++)
-                       {
-                               if (attrdef[i].adnum == parent_attno)
-                               {
-                                       this_default = attrdef[i].adbin;
-                                       break;
-                               }
-                       }
-                       Assert(this_default != NULL);
+       /* transform targetlist */
+       qry->targetList = transformTargetList(pstate, stmt->targetList);
 
-                       /*
-                        * If default expr could contain any vars, we'd need to fix 'em,
-                        * but it can't; so default is ready to apply to child.
-                        */
+       /* mark column origins */
+       markTargetListOrigins(pstate, qry->targetList);
 
-                       def->cooked_default = pstrdup(this_default);
-               }
-       }
+       /* transform WHERE */
+       qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
 
        /*
-        * Close the parent rel, but keep our AccessShareLock on it until xact
-        * commit.      That will prevent someone else from deleting or ALTERing the
-        * parent before the child is committed.
+        * Initial processing of HAVING clause is just like WHERE clause.
         */
-       heap_close(relation, NoLock);
-}
-
-static void
-transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
-{
-       IndexStmt  *index;
-       List       *indexlist = NIL;
-       ListCell   *listptr;
-       ListCell   *l;
+       qry->havingQual = transformWhereClause(pstate, stmt->havingClause,
+                                                                                  "HAVING");
 
        /*
-        * Run through the constraints that need to generate an index. For PRIMARY
-        * KEY, mark each column as NOT NULL and create an index. For UNIQUE,
-        * create an index as for PRIMARY KEY, but do not insist on NOT NULL.
+        * 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.
         */
-       foreach(listptr, cxt->ixconstraints)
-       {
-               Constraint *constraint = lfirst(listptr);
-               ListCell   *keys;
-               IndexElem  *iparam;
-
-               Assert(IsA(constraint, Constraint));
-               Assert((constraint->contype == CONSTR_PRIMARY)
-                          || (constraint->contype == CONSTR_UNIQUE));
-
-               index = makeNode(IndexStmt);
-
-               index->unique = true;
-               index->primary = (constraint->contype == CONSTR_PRIMARY);
-               if (index->primary)
-               {
-                       if (cxt->pkey != NULL)
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-                                                errmsg("multiple primary keys for table \"%s\" are not allowed",
-                                                               cxt->relation->relname)));
-                       cxt->pkey = index;
-
-                       /*
-                        * In ALTER TABLE case, a primary index might already exist, but
-                        * DefineIndex will check for it.
-                        */
-               }
-               index->isconstraint = true;
-
-               if (constraint->name != NULL)
-                       index->idxname = pstrdup(constraint->name);
-               else
-                       index->idxname = NULL;          /* DefineIndex will choose name */
-
-               index->relation = cxt->relation;
-               index->accessMethod = DEFAULT_INDEX_TYPE;
-               index->tableSpace = constraint->indexspace;
-               index->indexParams = NIL;
-               index->whereClause = NULL;
-
-               /*
-                * Make sure referenced keys exist.  If we are making a PRIMARY KEY
-                * index, also make sure they are NOT NULL, if possible. (Although we
-                * could leave it to DefineIndex to mark the columns NOT NULL, it's
-                * more efficient to get it right the first time.)
-                */
-               foreach(keys, constraint->keys)
-               {
-                       char       *key = strVal(lfirst(keys));
-                       bool            found = false;
-                       ColumnDef  *column = NULL;
-                       ListCell   *columns;
-
-                       foreach(columns, cxt->columns)
-                       {
-                               column = (ColumnDef *) lfirst(columns);
-                               Assert(IsA(column, ColumnDef));
-                               if (strcmp(column->colname, key) == 0)
-                               {
-                                       found = true;
-                                       break;
-                               }
-                       }
-                       if (found)
-                       {
-                               /* found column in the new table; force it to be NOT NULL */
-                               if (constraint->contype == CONSTR_PRIMARY)
-                                       column->is_not_null = TRUE;
-                       }
-                       else if (SystemAttributeByName(key, cxt->hasoids) != NULL)
-                       {
-                               /*
-                                * column will be a system column in the new table, so accept
-                                * it.  System columns can't ever be null, so no need to worry
-                                * about PRIMARY/NOT NULL constraint.
-                                */
-                               found = true;
-                       }
-                       else if (cxt->inhRelations)
-                       {
-                               /* try inherited tables */
-                               ListCell   *inher;
-
-                               foreach(inher, cxt->inhRelations)
-                               {
-                                       RangeVar   *inh = (RangeVar *) lfirst(inher);
-                                       Relation        rel;
-                                       int                     count;
-
-                                       Assert(IsA(inh, RangeVar));
-                                       rel = heap_openrv(inh, AccessShareLock);
-                                       if (rel->rd_rel->relkind != RELKIND_RELATION)
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                                                  errmsg("inherited relation \"%s\" is not a table",
-                                                                 inh->relname)));
-                                       for (count = 0; count < rel->rd_att->natts; count++)
-                                       {
-                                               Form_pg_attribute inhattr = rel->rd_att->attrs[count];
-                                               char       *inhname = NameStr(inhattr->attname);
-
-                                               if (inhattr->attisdropped)
-                                                       continue;
-                                               if (strcmp(key, inhname) == 0)
-                                               {
-                                                       found = true;
-
-                                                       /*
-                                                        * We currently have no easy way to force an
-                                                        * inherited column to be NOT NULL at creation, if
-                                                        * its parent wasn't so already. We leave it to
-                                                        * DefineIndex to fix things up in this case.
-                                                        */
-                                                       break;
-                                               }
-                                       }
-                                       heap_close(rel, NoLock);
-                                       if (found)
-                                               break;
-                               }
-                       }
-
-                       /*
-                        * In the ALTER TABLE case, don't complain about index keys not
-                        * created in the command; they may well exist already.
-                        * DefineIndex will complain about them if not, and will also take
-                        * care of marking them NOT NULL.
-                        */
-                       if (!found && !cxt->isalter)
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_UNDEFINED_COLUMN),
-                                                errmsg("column \"%s\" named in key does not exist",
-                                                               key)));
-
-                       /* Check for PRIMARY KEY(foo, foo) */
-                       foreach(columns, index->indexParams)
-                       {
-                               iparam = (IndexElem *) lfirst(columns);
-                               if (iparam->name && strcmp(key, iparam->name) == 0)
-                               {
-                                       if (index->primary)
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_DUPLICATE_COLUMN),
-                                                                errmsg("column \"%s\" appears twice in primary key constraint",
-                                                                               key)));
-                                       else
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_DUPLICATE_COLUMN),
-                                                                errmsg("column \"%s\" appears twice in unique constraint",
-                                                                               key)));
-                               }
-                       }
-
-                       /* OK, add it to the index definition */
-                       iparam = makeNode(IndexElem);
-                       iparam->name = pstrdup(key);
-                       iparam->expr = NULL;
-                       iparam->opclass = NIL;
-                       index->indexParams = lappend(index->indexParams, iparam);
-               }
-
-               indexlist = lappend(indexlist, index);
-       }
+       qry->sortClause = transformSortClause(pstate,
+                                                                                 stmt->sortClause,
+                                                                                 &qry->targetList,
+                                                                                 true /* fix unknowns */ ,
+                                                                                 false /* allow SQL92 rules */ );
 
-       /*
-        * Scan the index list and remove any redundant index specifications. This
-        * can happen if, for instance, the user writes UNIQUE PRIMARY KEY. A
-        * strict reading of SQL92 would suggest raising an error instead, but
-        * that strikes me as too anal-retentive. - tgl 2001-02-14
-        *
-        * XXX in ALTER TABLE case, it'd be nice to look for duplicate
-        * pre-existing indexes, too.
-        */
-       cxt->alist = NIL;
-       if (cxt->pkey != NULL)
-       {
-               /* Make sure we keep the PKEY index in preference to others... */
-               cxt->alist = list_make1(cxt->pkey);
-       }
+       qry->groupClause = transformGroupClause(pstate,
+                                                                                       stmt->groupClause,
+                                                                                       &qry->targetList,
+                                                                                       qry->sortClause,
+                                                                                       false /* allow SQL92 rules */ );
 
-       foreach(l, indexlist)
+       if (stmt->distinctClause == NIL)
        {
-               bool            keep = true;
-               ListCell   *k;
-
-               index = lfirst(l);
-
-               /* if it's pkey, it's already in cxt->alist */
-               if (index == cxt->pkey)
-                       continue;
-
-               foreach(k, cxt->alist)
-               {
-                       IndexStmt  *priorindex = lfirst(k);
-
-                       if (equal(index->indexParams, priorindex->indexParams))
-                       {
-                               /*
-                                * If the prior index is as yet unnamed, and this one is
-                                * named, then transfer the name to the prior index. This
-                                * ensures that if we have named and unnamed constraints,
-                                * we'll use (at least one of) the names for the index.
-                                */
-                               if (priorindex->idxname == NULL)
-                                       priorindex->idxname = index->idxname;
-                               keep = false;
-                               break;
-                       }
-               }
-
-               if (keep)
-                       cxt->alist = lappend(cxt->alist, index);
+               qry->distinctClause = NIL;
+               qry->hasDistinctOn = false;
        }
-}
-
-static void
-transformFKConstraints(ParseState *pstate, CreateStmtContext *cxt,
-                                          bool skipValidation, bool isAddConstraint)
-{
-       ListCell   *fkclist;
-
-       if (cxt->fkconstraints == NIL)
-               return;
-
-       /*
-        * If CREATE TABLE or adding a column with NULL default, we can safely
-        * skip validation of the constraint.
-        */
-       if (skipValidation)
+       else if (linitial(stmt->distinctClause) == NULL)
        {
-               foreach(fkclist, cxt->fkconstraints)
-               {
-                       FkConstraint *fkconstraint = (FkConstraint *) lfirst(fkclist);
-
-                       fkconstraint->skip_validation = true;
-               }
+               /* We had SELECT DISTINCT */
+               qry->distinctClause = transformDistinctClause(pstate,
+                                                                                                         &qry->targetList,
+                                                                                                         qry->sortClause,
+                                                                                                         false);
+               qry->hasDistinctOn = false;
        }
-
-       /*
-        * For CREATE TABLE or ALTER TABLE ADD COLUMN, gin up an ALTER TABLE ADD
-        * CONSTRAINT command to execute after the basic command is complete. (If
-        * called from ADD CONSTRAINT, that routine will add the FK constraints to
-        * its own subcommand list.)
-        *
-        * Note: the ADD CONSTRAINT command must also execute after any index
-        * creation commands.  Thus, this should run after
-        * transformIndexConstraints, so that the CREATE INDEX commands are
-        * already in cxt->alist.
-        */
-       if (!isAddConstraint)
+       else
        {
-               AlterTableStmt *alterstmt = makeNode(AlterTableStmt);
-
-               alterstmt->relation = cxt->relation;
-               alterstmt->cmds = NIL;
-               alterstmt->relkind = OBJECT_TABLE;
-
-               foreach(fkclist, cxt->fkconstraints)
-               {
-                       FkConstraint *fkconstraint = (FkConstraint *) lfirst(fkclist);
-                       AlterTableCmd *altercmd = makeNode(AlterTableCmd);
-
-                       altercmd->subtype = AT_ProcessedConstraint;
-                       altercmd->name = NULL;
-                       altercmd->def = (Node *) fkconstraint;
-                       alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
-               }
-
-               cxt->alist = lappend(cxt->alist, alterstmt);
+               /* We had SELECT DISTINCT ON */
+               qry->distinctClause = transformDistinctOnClause(pstate,
+                                                                                                               stmt->distinctClause,
+                                                                                                               &qry->targetList,
+                                                                                                               qry->sortClause);
+               qry->hasDistinctOn = true;
        }
-}
 
-/*
- * transformIndexStmt -
- *       transforms the qualification of the index statement
- */
-static Query *
-transformIndexStmt(ParseState *pstate, IndexStmt *stmt)
-{
-       Query      *qry;
-       RangeTblEntry *rte = NULL;
-       ListCell   *l;
+       /* transform LIMIT */
+       qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
+                                                                                       "OFFSET");
+       qry->limitCount = transformLimitClause(pstate, stmt->limitCount,
+                                                                                  "LIMIT");
 
-       qry = makeNode(Query);
-       qry->commandType = CMD_UTILITY;
+       /* transform window clauses after we have seen all window functions */
+       qry->windowClause = transformWindowDefinitions(pstate,
+                                                                                                  pstate->p_windowdefs,
+                                                                                                  &qry->targetList);
 
-       /* take care of the where clause */
-       if (stmt->whereClause)
+       /* handle any SELECT INTO/CREATE TABLE AS spec */
+       if (stmt->intoClause)
        {
-               /*
-                * Put the parent table into the rtable so that the WHERE clause can
-                * refer to its fields without qualification.  Note that this only
-                * works if the parent table already exists --- so we can't easily
-                * support predicates on indexes created implicitly by CREATE TABLE.
-                * Fortunately, that's not necessary.
-                */
-               rte = addRangeTableEntry(pstate, stmt->relation, NULL, false, true);
-
-               /* no to join list, yes to namespaces */
-               addRTEtoQuery(pstate, rte, false, true, true);
-
-               stmt->whereClause = transformWhereClause(pstate, stmt->whereClause,
-                                                                                                "WHERE");
+               qry->intoClause = stmt->intoClause;
+               if (stmt->intoClause->colNames)
+                       applyColumnNames(qry->targetList, stmt->intoClause->colNames);
        }
 
-       /* take care of any index expressions */
-       foreach(l, stmt->indexParams)
-       {
-               IndexElem  *ielem = (IndexElem *) lfirst(l);
-
-               if (ielem->expr)
-               {
-                       /* Set up rtable as for predicate, see notes above */
-                       if (rte == NULL)
-                       {
-                               rte = addRangeTableEntry(pstate, stmt->relation, NULL,
-                                                                                false, true);
-                               /* no to join list, yes to namespaces */
-                               addRTEtoQuery(pstate, rte, false, true, true);
-                       }
-                       ielem->expr = transformExpr(pstate, ielem->expr);
-
-                       /*
-                        * We check only that the result type is legitimate; this is for
-                        * consistency with what transformWhereClause() checks for the
-                        * predicate.  DefineIndex() will make more checks.
-                        */
-                       if (expression_returns_set(ielem->expr))
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                                errmsg("index expression may not return a set")));
-               }
-       }
+       qry->rtable = pstate->p_rtable;
+       qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
        qry->hasSubLinks = pstate->p_hasSubLinks;
-       stmt->rangetable = pstate->p_rtable;
+       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);
 
-       qry->utilityStmt = (Node *) stmt;
+       foreach(l, stmt->lockingClause)
+       {
+               transformLockingClause(pstate, qry,
+                                                          (LockingClause *) lfirst(l), false);
+       }
 
        return qry;
 }
 
 /*
- * transformRuleStmt -
- *       transform a Create Rule Statement. The actions is a list of parse
- *       trees which is transformed into a list of query trees.
+ * transformValuesClause -
+ *       transforms a VALUES clause that's being used as a standalone SELECT
+ *
+ * We build a Query containing a VALUES RTE, rather as if one had written
+ *                     SELECT * FROM (VALUES ...)
  */
 static Query *
-transformRuleStmt(ParseState *pstate, RuleStmt *stmt,
-                                 List **extras_before, List **extras_after)
+transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 {
-       Query      *qry;
-       Relation        rel;
-       RangeTblEntry *oldrte;
-       RangeTblEntry *newrte;
-
-       qry = makeNode(Query);
-       qry->commandType = CMD_UTILITY;
-       qry->utilityStmt = (Node *) stmt;
-
-       /*
-        * To avoid deadlock, make sure the first thing we do is grab
-        * AccessExclusiveLock on the target relation.  This will be needed by
-        * DefineQueryRewrite(), and we don't want to grab a lesser lock
-        * beforehand.
-        */
-       rel = heap_openrv(stmt->relation, AccessExclusiveLock);
+       Query      *qry = makeNode(Query);
+       List       *exprsLists = NIL;
+       List      **colexprs = NULL;
+       Oid                *coltypes = NULL;
+       int                     sublist_length = -1;
+       List       *newExprsLists;
+       RangeTblEntry *rte;
+       RangeTblRef *rtr;
+       ListCell   *lc;
+       ListCell   *lc2;
+       int                     i;
 
-       /*
-        * NOTE: 'OLD' must always have a varno equal to 1 and 'NEW' equal to 2.
-        * Set up their RTEs in the main pstate for use in parsing the rule
-        * qualification.
-        */
-       Assert(pstate->p_rtable == NIL);
-       oldrte = addRangeTableEntryForRelation(pstate, rel,
-                                                                                  makeAlias("*OLD*", NIL),
-                                                                                  false, false);
-       newrte = addRangeTableEntryForRelation(pstate, rel,
-                                                                                  makeAlias("*NEW*", NIL),
-                                                                                  false, false);
-       /* Must override addRangeTableEntry's default access-check flags */
-       oldrte->requiredPerms = 0;
-       newrte->requiredPerms = 0;
+       qry->commandType = CMD_SELECT;
 
-       /*
-        * They must be in the namespace too for lookup purposes, but only add the
-        * one(s) that are relevant for the current kind of rule.  In an UPDATE
-        * rule, quals must refer to OLD.field or NEW.field to be unambiguous, but
-        * there's no need to be so picky for INSERT & DELETE.  We do not add them
-        * to the joinlist.
-        */
-       switch (stmt->event)
+       /* Most SELECT stuff doesn't apply in a VALUES clause */
+       Assert(stmt->distinctClause == NIL);
+       Assert(stmt->targetList == NIL);
+       Assert(stmt->fromClause == NIL);
+       Assert(stmt->whereClause == NULL);
+       Assert(stmt->groupClause == NIL);
+       Assert(stmt->havingClause == NULL);
+       Assert(stmt->windowClause == NIL);
+       Assert(stmt->op == SETOP_NONE);
+
+       /* process the WITH clause independently of all else */
+       if (stmt->withClause)
        {
-               case CMD_SELECT:
-                       addRTEtoQuery(pstate, oldrte, false, true, true);
-                       break;
-               case CMD_UPDATE:
-                       addRTEtoQuery(pstate, oldrte, false, true, true);
-                       addRTEtoQuery(pstate, newrte, false, true, true);
-                       break;
-               case CMD_INSERT:
-                       addRTEtoQuery(pstate, newrte, false, true, true);
-                       break;
-               case CMD_DELETE:
-                       addRTEtoQuery(pstate, oldrte, false, true, true);
-                       break;
-               default:
-                       elog(ERROR, "unrecognized event type: %d",
-                                (int) stmt->event);
-                       break;
+               qry->hasRecursive = stmt->withClause->recursive;
+               qry->cteList = transformWithClause(pstate, stmt->withClause);
        }
 
-       /* take care of the where clause */
-       stmt->whereClause = transformWhereClause(pstate, stmt->whereClause,
-                                                                                        "WHERE");
-
-       if (list_length(pstate->p_rtable) != 2)         /* naughty, naughty... */
-               ereport(ERROR,
-                               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-                                errmsg("rule WHERE condition may not contain references to other relations")));
-
-       /* aggregates not allowed (but subselects are okay) */
-       if (pstate->p_hasAggs)
-               ereport(ERROR,
-                               (errcode(ERRCODE_GROUPING_ERROR),
-               errmsg("rule WHERE condition may not contain aggregate functions")));
-
-       /* save info about sublinks in where clause */
-       qry->hasSubLinks = pstate->p_hasSubLinks;
-
        /*
-        * 'instead nothing' rules with a qualification need a query rangetable so
-        * the rewrite handler can add the negated rule qualification to the
-        * original query. We create a query with the new command type CMD_NOTHING
-        * here that is treated specially by the rewrite system.
+        * For each row of VALUES, transform the raw expressions and gather type
+        * information.  This is also a handy place to reject DEFAULT nodes, which
+        * the grammar allows for simplicity.
         */
-       if (stmt->actions == NIL)
-       {
-               Query      *nothing_qry = makeNode(Query);
-
-               nothing_qry->commandType = CMD_NOTHING;
-               nothing_qry->rtable = pstate->p_rtable;
-               nothing_qry->jointree = makeFromExpr(NIL, NULL);                /* no join wanted */
-
-               stmt->actions = list_make1(nothing_qry);
-       }
-       else
+       foreach(lc, stmt->valuesLists)
        {
-               ListCell   *l;
-               List       *newactions = NIL;
-
-               /*
-                * transform each statement, like parse_sub_analyze()
-                */
-               foreach(l, stmt->actions)
-               {
-                       Node       *action = (Node *) lfirst(l);
-                       ParseState *sub_pstate = make_parsestate(pstate->parentParseState);
-                       Query      *sub_qry,
-                                          *top_subqry;
-                       bool            has_old,
-                                               has_new;
-
-                       /*
-                        * Set up OLD/NEW in the rtable for this statement.  The entries
-                        * are added only to relnamespace, not varnamespace, because we
-                        * don't want them to be referred to by unqualified field names
-                        * nor "*" in the rule actions.  We decide later whether to put
-                        * them in the joinlist.
-                        */
-                       oldrte = addRangeTableEntryForRelation(sub_pstate, rel,
-                                                                                                  makeAlias("*OLD*", NIL),
-                                                                                                  false, false);
-                       newrte = addRangeTableEntryForRelation(sub_pstate, rel,
-                                                                                                  makeAlias("*NEW*", NIL),
-                                                                                                  false, false);
-                       oldrte->requiredPerms = 0;
-                       newrte->requiredPerms = 0;
-                       addRTEtoQuery(sub_pstate, oldrte, false, true, false);
-                       addRTEtoQuery(sub_pstate, newrte, false, true, false);
-
-                       /* Transform the rule action statement */
-                       top_subqry = transformStmt(sub_pstate, action,
-                                                                          extras_before, extras_after);
-
-                       /*
-                        * We cannot support utility-statement actions (eg NOTIFY) with
-                        * nonempty rule WHERE conditions, because there's no way to make
-                        * the utility action execute conditionally.
-                        */
-                       if (top_subqry->commandType == CMD_UTILITY &&
-                               stmt->whereClause != NULL)
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-                                                errmsg("rules with WHERE conditions may only have SELECT, INSERT, UPDATE, or DELETE actions")));
+               List       *sublist = (List *) lfirst(lc);
 
-                       /*
-                        * If the action is INSERT...SELECT, OLD/NEW have been pushed down
-                        * into the SELECT, and that's what we need to look at. (Ugly
-                        * kluge ... try to fix this when we redesign querytrees.)
-                        */
-                       sub_qry = getInsertSelectQuery(top_subqry, NULL);
-
-                       /*
-                        * If the sub_qry is a setop, we cannot attach any qualifications
-                        * to it, because the planner won't notice them.  This could
-                        * perhaps be relaxed someday, but for now, we may as well reject
-                        * such a rule immediately.
-                        */
-                       if (sub_qry->setOperations != NULL && stmt->whereClause != NULL)
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented")));
-
-                       /*
-                        * Validate action's use of OLD/NEW, qual too
-                        */
-                       has_old =
-                               rangeTableEntry_used((Node *) sub_qry, PRS2_OLD_VARNO, 0) ||
-                               rangeTableEntry_used(stmt->whereClause, PRS2_OLD_VARNO, 0);
-                       has_new =
-                               rangeTableEntry_used((Node *) sub_qry, PRS2_NEW_VARNO, 0) ||
-                               rangeTableEntry_used(stmt->whereClause, PRS2_NEW_VARNO, 0);
-
-                       switch (stmt->event)
-                       {
-                               case CMD_SELECT:
-                                       if (has_old)
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-                                                                errmsg("ON SELECT rule may not use OLD")));
-                                       if (has_new)
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-                                                                errmsg("ON SELECT rule may not use NEW")));
-                                       break;
-                               case CMD_UPDATE:
-                                       /* both are OK */
-                                       break;
-                               case CMD_INSERT:
-                                       if (has_old)
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-                                                                errmsg("ON INSERT rule may not use OLD")));
-                                       break;
-                               case CMD_DELETE:
-                                       if (has_new)
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-                                                                errmsg("ON DELETE rule may not use NEW")));
-                                       break;
-                               default:
-                                       elog(ERROR, "unrecognized event type: %d",
-                                                (int) stmt->event);
-                                       break;
-                       }
-
-                       /*
-                        * For efficiency's sake, add OLD to the rule action's jointree
-                        * only if it was actually referenced in the statement or qual.
-                        *
-                        * For INSERT, NEW is not really a relation (only a reference to
-                        * the to-be-inserted tuple) and should never be added to the
-                        * jointree.
-                        *
-                        * For UPDATE, we treat NEW as being another kind of reference to
-                        * OLD, because it represents references to *transformed* tuples
-                        * of the existing relation.  It would be wrong to enter NEW
-                        * separately in the jointree, since that would cause a double
-                        * join of the updated relation.  It's also wrong to fail to make
-                        * a jointree entry if only NEW and not OLD is mentioned.
-                        */
-                       if (has_old || (has_new && stmt->event == CMD_UPDATE))
-                       {
-                               /*
-                                * If sub_qry is a setop, manipulating its jointree will do no
-                                * good at all, because the jointree is dummy. (This should be
-                                * a can't-happen case because of prior tests.)
-                                */
-                               if (sub_qry->setOperations != NULL)
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                        errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented")));
-                               /* hack so we can use addRTEtoQuery() */
-                               sub_pstate->p_rtable = sub_qry->rtable;
-                               sub_pstate->p_joinlist = sub_qry->jointree->fromlist;
-                               addRTEtoQuery(sub_pstate, oldrte, true, false, false);
-                               sub_qry->jointree->fromlist = sub_pstate->p_joinlist;
-                       }
-
-                       newactions = lappend(newactions, top_subqry);
-
-                       release_pstate_resources(sub_pstate);
-                       pfree(sub_pstate);
-               }
-
-               stmt->actions = newactions;
-       }
-
-       /* Close relation, but keep the exclusive lock */
-       heap_close(rel, NoLock);
-
-       return qry;
-}
+               /* Do basic expression transformation (same as a ROW() expr) */
+               sublist = transformExpressionList(pstate, sublist);
 
+               /*
+                * All the sublists must be the same length, *after* transformation
+                * (which might expand '*' into multiple items).  The VALUES RTE can't
+                * handle anything different.
+                */
+               if (sublist_length < 0)
+               {
+                       /* Remember post-transformation length of first sublist */
+                       sublist_length = list_length(sublist);
+                       /* and allocate arrays for 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"),
+                                        parser_errposition(pstate,
+                                                                               exprLocation((Node *) sublist))));
+               }
 
-/*
- * transformSelectStmt -
- *       transforms a Select Statement
- *
- * Note: this is also used for DECLARE CURSOR statements.
- */
-static Query *
-transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
-{
-       Query      *qry = makeNode(Query);
-       Node       *qual;
-
-       qry->commandType = CMD_SELECT;
+               exprsLists = lappend(exprsLists, sublist);
 
-       /* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
-       pstate->p_locking_clause = stmt->lockingClause;
+               /* Check for DEFAULT and build per-column expression lists */
+               i = 0;
+               foreach(lc2, sublist)
+               {
+                       Node       *col = (Node *) lfirst(lc2);
 
-       /* process the FROM clause */
-       transformFromClause(pstate, stmt->fromClause);
+                       if (IsA(col, SetToDefault))
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("DEFAULT can only appear in a VALUES list within INSERT"),
+                                                parser_errposition(pstate, exprLocation(col))));
+                       colexprs[i] = lappend(colexprs[i], col);
+                       i++;
+               }
+       }
 
-       /* transform targetlist */
-       qry->targetList = transformTargetList(pstate, stmt->targetList);
+       /*
+        * Now resolve the common types of the columns, and coerce everything to
+        * those types.
+        */
+       for (i = 0; i < sublist_length; i++)
+       {
+               coltypes[i] = select_common_type(pstate, colexprs[i], "VALUES", NULL);
+       }
 
-       /* handle any SELECT INTO/CREATE TABLE AS spec */
-       qry->into = stmt->into;
-       if (stmt->intoColNames)
-               applyColumnNames(qry->targetList, stmt->intoColNames);
+       newExprsLists = NIL;
+       foreach(lc, exprsLists)
+       {
+               List       *sublist = (List *) lfirst(lc);
+               List       *newsublist = NIL;
 
-       qry->intoHasOids = interpretOidsOption(stmt->intoHasOids);
-       qry->intoOnCommit = stmt->intoOnCommit;
-       qry->intoTableSpaceName = stmt->intoTableSpaceName;
+               i = 0;
+               foreach(lc2, sublist)
+               {
+                       Node       *col = (Node *) lfirst(lc2);
 
-       /* mark column origins */
-       markTargetListOrigins(pstate, qry->targetList);
+                       col = coerce_to_common_type(pstate, col, coltypes[i], "VALUES");
+                       newsublist = lappend(newsublist, col);
+                       i++;
+               }
 
-       /* transform WHERE */
-       qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
+               newExprsLists = lappend(newExprsLists, newsublist);
+       }
 
        /*
-        * Initial processing of HAVING clause is just like WHERE clause.
+        * Generate the VALUES RTE
         */
-       qry->havingQual = transformWhereClause(pstate, stmt->havingClause,
-                                                                                  "HAVING");
+       rte = addRangeTableEntryForValues(pstate, newExprsLists, NULL, true);
+       rtr = makeNode(RangeTblRef);
+       /* assume new rte is at end */
+       rtr->rtindex = list_length(pstate->p_rtable);
+       Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
+       pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
+       pstate->p_varnamespace = lappend(pstate->p_varnamespace, rte);
 
        /*
-        * Transform sorting/grouping stuff.  Do ORDER BY first because both
-        * transformGroupClause and transformDistinctClause need the results.
+        * Generate a targetlist as though expanding "*"
+        */
+       Assert(pstate->p_next_resno == 1);
+       qry->targetList = expandRelAttrs(pstate, rte, rtr->rtindex, 0, -1);
+
+       /*
+        * The grammar allows attaching ORDER BY, LIMIT, and FOR UPDATE to a
+        * VALUES, so cope.
         */
        qry->sortClause = transformSortClause(pstate,
                                                                                  stmt->sortClause,
                                                                                  &qry->targetList,
-                                                                                 true /* fix unknowns */ );
-
-       qry->groupClause = transformGroupClause(pstate,
-                                                                                       stmt->groupClause,
-                                                                                       &qry->targetList,
-                                                                                       qry->sortClause);
-
-       qry->distinctClause = transformDistinctClause(pstate,
-                                                                                                 stmt->distinctClause,
-                                                                                                 &qry->targetList,
-                                                                                                 &qry->sortClause);
+                                                                                 true /* fix unknowns */ ,
+                                                                                 false /* allow SQL92 rules */ );
 
        qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
                                                                                        "OFFSET");
        qry->limitCount = transformLimitClause(pstate, stmt->limitCount,
                                                                                   "LIMIT");
 
+       if (stmt->lockingClause)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                        errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES")));
+
+       /* handle any CREATE TABLE AS spec */
+       if (stmt->intoClause)
+       {
+               qry->intoClause = stmt->intoClause;
+               if (stmt->intoClause->colNames)
+                       applyColumnNames(qry->targetList, stmt->intoClause->colNames);
+       }
+
+       /*
+        * There mustn't have been any table references in the expressions, else
+        * strange things would happen, like Cartesian products of those tables
+        * with the VALUES list.  We have to check this after parsing ORDER BY et
+        * al since those could insert more junk.
+        */
+       if (list_length(pstate->p_joinlist) != 1)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("VALUES must not contain table references"),
+                                parser_errposition(pstate,
+                                                  locate_var_of_level((Node *) newExprsLists, 0))));
+
+       /*
+        * Another thing we can't currently support is NEW/OLD references in rules
+        * --- seems we'd need something like SQL99's LATERAL construct to ensure
+        * that the values would be available while evaluating the VALUES RTE.
+        * This is a shame.  FIXME
+        */
+       if (list_length(pstate->p_rtable) != 1 &&
+               contain_vars_of_level((Node *) newExprsLists, 0))
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("VALUES must not contain OLD or NEW references"),
+                                errhint("Use SELECT ... UNION ALL ... instead."),
+                                parser_errposition(pstate,
+                                                  locate_var_of_level((Node *) newExprsLists, 0))));
+
        qry->rtable = pstate->p_rtable;
-       qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
+       qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
        qry->hasSubLinks = pstate->p_hasSubLinks;
-       qry->hasAggs = pstate->p_hasAggs;
-       if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
-               parseCheckAggregates(pstate, qry);
-
-       if (stmt->lockingClause)
-               transformLockingClause(qry, stmt->lockingClause);
+       /* aggregates not allowed (but subselects are okay) */
+       if (pstate->p_hasAggs)
+               ereport(ERROR,
+                               (errcode(ERRCODE_GROUPING_ERROR),
+                                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;
 }
 
 /*
- * transformSetOperationsStmt -
+ * transformSetOperationStmt -
  *       transforms a set-operations tree
  *
  * A set-operation tree is just a SELECT, but with UNION/INTERSECT/EXCEPT
@@ -1894,25 +1183,34 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
        int                     leftmostRTI;
        Query      *leftmostQuery;
        SetOperationStmt *sostmt;
-       RangeVar   *into;
-       List       *intoColNames;
+       List       *socolinfo;
+       List       *intoColNames = NIL;
        List       *sortClause;
        Node       *limitOffset;
        Node       *limitCount;
-       LockingClause *lockingClause;
+       List       *lockingClause;
        Node       *node;
        ListCell   *left_tlist,
-                          *dtlist;
+                          *lct,
+                          *lcm,
+                          *l;
        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.
@@ -1922,12 +1220,14 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
                leftmostSelect = leftmostSelect->larg;
        Assert(leftmostSelect && IsA(leftmostSelect, SelectStmt) &&
                   leftmostSelect->larg == NULL);
-       into = leftmostSelect->into;
-       intoColNames = leftmostSelect->intoColNames;
+       if (leftmostSelect->intoClause)
+       {
+               qry->intoClause = leftmostSelect->intoClause;
+               intoColNames = leftmostSelect->intoClause->colNames;
+       }
 
-       /* clear them to prevent complaints in transformSetOperationTree() */
-       leftmostSelect->into = NULL;
-       leftmostSelect->intoColNames = NIL;
+       /* clear this to prevent complaints in transformSetOperationTree() */
+       leftmostSelect->intoClause = NULL;
 
        /*
         * These are not one-time, exactly, but we want to process them here and
@@ -1942,7 +1242,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
        stmt->sortClause = NIL;
        stmt->limitOffset = NULL;
        stmt->limitCount = NULL;
-       stmt->lockingClause = NULL;
+       stmt->lockingClause = NIL;
 
        /* We don't support FOR UPDATE/SHARE with set ops at the moment. */
        if (lockingClause)
@@ -1953,7 +1253,9 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
        /*
         * 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;
 
@@ -1984,45 +1286,33 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
        targetnames = NIL;
        left_tlist = list_head(leftmostQuery->targetList);
 
-       foreach(dtlist, sostmt->colTypes)
+       forboth(lct, sostmt->colTypes, lcm, sostmt->colTypmods)
        {
-               Oid                     colType = lfirst_oid(dtlist);
+               Oid                     colType = lfirst_oid(lct);
+               int32           colTypmod = lfirst_int(lcm);
                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,
-                                                               -1,
-                                                               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);
        }
 
-       /*
-        * Handle SELECT INTO/CREATE TABLE AS.
-        *
-        * Any column names from CREATE TABLE AS need to be attached to both the
-        * top level and the leftmost subquery.  We do not do this earlier because
-        * we do *not* want the targetnames list to be affected.
-        */
-       qry->into = into;
-       if (intoColNames)
-       {
-               applyColumnNames(qry->targetList, intoColNames);
-               applyColumnNames(leftmostQuery->targetList, intoColNames);
-       }
-
        /*
         * As a first step towards supporting sort clauses that are expressions
         * using the output columns, generate a varnamespace entry that makes the
@@ -2033,16 +1323,15 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
         * "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 */
 
@@ -2060,22 +1349,40 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
        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;
 
        if (tllen != list_length(qry->targetList))
                ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                errmsg("ORDER BY on a UNION/INTERSECT/EXCEPT result must be on one of the result columns")));
+                                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."),
+                                parser_errposition(pstate,
+                                                  exprLocation(list_nth(qry->targetList, tllen)))));
 
        qry->limitOffset = transformLimitClause(pstate, limitOffset,
                                                                                        "OFFSET");
        qry->limitCount = transformLimitClause(pstate, limitCount,
                                                                                   "LIMIT");
 
+       /*
+        * Handle SELECT INTO/CREATE TABLE AS.
+        *
+        * Any column names from CREATE TABLE AS need to be attached to both the
+        * top level and the leftmost subquery.  We do not do this earlier because
+        * we do *not* want sortClause processing to be affected.
+        */
+       if (intoColNames)
+       {
+               applyColumnNames(qry->targetList, intoColNames);
+               applyColumnNames(leftmostQuery->targetList, intoColNames);
+       }
+
        qry->rtable = pstate->p_rtable;
        qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
@@ -2083,9 +1390,15 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
        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);
 
-       if (lockingClause)
-               transformLockingClause(qry, lockingClause);
+       foreach(l, lockingClause)
+       {
+               transformLockingClause(pstate, qry,
+                                                          (LockingClause *) lfirst(l), false);
+       }
 
        return qry;
 }
@@ -2093,9 +1406,17 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 /*
  * 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;
 
@@ -2104,10 +1425,13 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
        /*
         * Validity-check both leaf and internal SELECTs for disallowed ops.
         */
-       if (stmt->into)
+       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,
@@ -2115,7 +1439,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
                                 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.
@@ -2138,11 +1462,11 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
        if (isLeaf)
        {
                /* Process leaf SELECT */
-               List       *selectList;
                Query      *selectQuery;
                char            selectName[32];
                RangeTblEntry *rte;
                RangeTblRef *rtr;
+               ListCell   *tl;
 
                /*
                 * Transform SelectStmt into a Query.
@@ -2151,11 +1475,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
                 * of this sub-query, because they are not in the toplevel pstate's
                 * namespace list.
                 */
-               selectList = parse_sub_analyze((Node *) stmt, pstate);
-
-               Assert(list_length(selectList) == 1);
-               selectQuery = (Query *) linitial(selectList);
-               Assert(IsA(selectQuery, Query));
+               selectQuery = parse_sub_analyze((Node *) stmt, pstate, NULL, false);
 
                /*
                 * Check for bogus references to Vars on the current query level (but
@@ -2168,7 +1488,21 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
                        if (contain_vars_of_level((Node *) selectQuery, 1))
                                ereport(ERROR,
                                                (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
-                                                errmsg("UNION/INTERSECT/EXCEPT member statement may not 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);
                }
 
                /*
@@ -2194,10 +1528,10 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
        {
                /* Process an internal node (set operation node) */
                SetOperationStmt *op = makeNode(SetOperationStmt);
-               List       *lcoltypes;
-               List       *rcoltypes;
-               ListCell   *l;
-               ListCell   *r;
+               List       *lcolinfo;
+               List       *rcolinfo;
+               ListCell   *lci;
+               ListCell   *rci;
                const char *context;
 
                context = (stmt->op == SETOP_UNION ? "UNION" :
@@ -2208,33 +1542,133 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
                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.
                 */
-               op->larg = transformSetOperationTree(pstate, stmt->larg);
-               op->rarg = transformSetOperationTree(pstate, stmt->rarg);
+               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);
 
                /*
                 * Verify that the two children have the same number of non-junk
                 * columns, and determine the types of the merged output columns.
                 */
-               lcoltypes = getSetColTypes(pstate, op->larg);
-               rcoltypes = getSetColTypes(pstate, op->rarg);
-               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)));
+                                               context),
+                                        parser_errposition(pstate,
+                                                                               exprLocation((Node *) rcolinfo))));
 
+               *colInfo = NIL;
                op->colTypes = NIL;
-               forboth(l, lcoltypes, r, rcoltypes)
+               op->colTypmods = NIL;
+               op->groupClauses = NIL;
+               forboth(lci, lcolinfo, rci, rcolinfo)
                {
-                       Oid                     lcoltype = lfirst_oid(l);
-                       Oid                     rcoltype = lfirst_oid(r);
+                       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(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);
 
-                       rescoltype = select_common_type(list_make2_oid(lcoltype, rcoltype),
-                                                                                       context);
                        op->colTypes = lappend_oid(op->colTypes, rescoltype);
+                       op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
+
+                       /*
+                        * 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;
@@ -2242,67 +1676,96 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
 }
 
 /*
- * getSetColTypes
- *             Get output column types 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 List *
-getSetColTypes(ParseState *pstate, Node *node)
+static void
+determineRecursiveColTypes(ParseState *pstate, Node *larg, List *lcolinfo)
 {
-       if (IsA(node, RangeTblRef))
-       {
-               RangeTblRef *rtr = (RangeTblRef *) node;
-               RangeTblEntry *rte = rt_fetch(rtr->rtindex, pstate->p_rtable);
-               Query      *selectQuery = rte->subquery;
-               List       *result = NIL;
-               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;
-                       result = lappend_oid(result, exprType((Node *) tle->expr));
-               }
-               return result;
-       }
-       else if (IsA(node, SetOperationStmt))
-       {
-               SetOperationStmt *op = (SetOperationStmt *) node;
+       /*
+        * 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;
 
-               /* Result already computed during transformation of node */
-               Assert(op->colTypes != NIL);
-               return op->colTypes;
-       }
-       else
+       foreach(lci, lcolinfo)
        {
-               elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
-               return NIL;                             /* keep compiler quiet */
+               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 */
+/*
+ * Attach column names from a ColumnDef list to a TargetEntry list
+ * (for CREATE TABLE AS)
+ */
 static void
 applyColumnNames(List *dst, List *src)
 {
        ListCell   *dst_item;
        ListCell   *src_item;
 
-       if (list_length(src) > list_length(dst))
-               ereport(ERROR,
-                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                errmsg("CREATE TABLE AS specifies too many column names")));
+       src_item = list_head(src);
 
-       forboth(dst_item, dst, src_item, src)
+       foreach(dst_item, dst)
        {
                TargetEntry *d = (TargetEntry *) lfirst(dst_item);
-               ColumnDef  *s = (ColumnDef *) lfirst(src_item);
+               ColumnDef  *s;
+
+               /* junk targets don't count */
+               if (d->resjunk)
+                       continue;
+
+               /* fewer ColumnDefs than target entries is OK */
+               if (src_item == NULL)
+                       break;
+
+               s = (ColumnDef *) lfirst(src_item);
+               src_item = lnext(src_item);
 
-               Assert(!d->resjunk);
                d->resname = pstrdup(s->colname);
        }
+
+       /* more ColumnDefs than target entries is not OK */
+       if (src_item != NULL)
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("CREATE TABLE AS specifies too many column names")));
 }
 
 
@@ -2314,6 +1777,7 @@ static Query *
 transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 {
        Query      *qry = makeNode(Query);
+       RangeTblEntry *target_rte;
        Node       *qual;
        ListCell   *origTargetList;
        ListCell   *tl;
@@ -2336,13 +1800,30 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 
        qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
 
+       qry->returningList = transformReturningList(pstate, stmt->returningList);
+
        qry->rtable = pstate->p_rtable;
        qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
        qry->hasSubLinks = pstate->p_hasSubLinks;
-       qry->hasAggs = pstate->p_hasAggs;
+
+       /*
+        * Top-level aggregates are simply disallowed in UPDATE, per spec. (From
+        * an implementation point of view, this is forced because the implicit
+        * ctid reference would otherwise be an ungrouped variable.)
+        */
        if (pstate->p_hasAggs)
-               parseCheckAggregates(pstate, qry);
+               ereport(ERROR,
+                               (errcode(ERRCODE_GROUPING_ERROR),
+                                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
@@ -2354,13 +1835,14 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
                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)
        {
                TargetEntry *tle = (TargetEntry *) lfirst(tl);
                ResTarget  *origTarget;
-               int             attrno;
+               int                     attrno;
 
                if (tle->resjunk)
                {
@@ -2386,197 +1868,122 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
                                        (errcode(ERRCODE_UNDEFINED_COLUMN),
                                         errmsg("column \"%s\" of relation \"%s\" does not exist",
                                                        origTarget->name,
-                                                       RelationGetRelationName(pstate->p_target_relation)),
-                                        parser_errposition(pstate, origTarget->location)));
-
-               updateTargetListEntry(pstate, tle, origTarget->name,
-                                                         attrno,
-                                                         origTarget->indirection,
-                                                         origTarget->location);
-
-               origTargetList = lnext(origTargetList);
-       }
-       if (origTargetList != NULL)
-               elog(ERROR, "UPDATE target count mismatch --- internal error");
-
-       return qry;
-}
-
-/*
- * tranformAlterTableStmt -
- *     transform an Alter Table Statement
- */
-static Query *
-transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt,
-                                               List **extras_before, List **extras_after)
-{
-       CreateStmtContext cxt;
-       Query      *qry;
-       ListCell   *lcmd,
-                          *l;
-       List       *newcmds = NIL;
-       bool            skipValidation = true;
-       AlterTableCmd *newcmd;
-
-       cxt.stmtType = "ALTER TABLE";
-       cxt.relation = stmt->relation;
-       cxt.inhRelations = NIL;
-       cxt.isalter = true;
-       cxt.hasoids = false;            /* need not be right */
-       cxt.columns = NIL;
-       cxt.ckconstraints = NIL;
-       cxt.fkconstraints = NIL;
-       cxt.ixconstraints = NIL;
-       cxt.blist = NIL;
-       cxt.alist = NIL;
-       cxt.pkey = NULL;
-
-       /*
-        * The only subtypes that currently require parse transformation handling
-        * are ADD COLUMN and ADD CONSTRAINT.  These largely re-use code from
-        * CREATE TABLE.
-        */
-       foreach(lcmd, stmt->cmds)
-       {
-               AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
-
-               switch (cmd->subtype)
-               {
-                       case AT_AddColumn:
-                               {
-                                       ColumnDef  *def = (ColumnDef *) cmd->def;
-
-                                       Assert(IsA(cmd->def, ColumnDef));
-                                       transformColumnDefinition(pstate, &cxt,
-                                                                                         (ColumnDef *) cmd->def);
-
-                                       /*
-                                        * If the column has a non-null default, we can't skip
-                                        * validation of foreign keys.
-                                        */
-                                       if (((ColumnDef *) cmd->def)->raw_default != NULL)
-                                               skipValidation = false;
-
-                                       newcmds = lappend(newcmds, cmd);
-
-                                       /*
-                                        * Convert an ADD COLUMN ... NOT NULL constraint to a
-                                        * separate command
-                                        */
-                                       if (def->is_not_null)
-                                       {
-                                               /* Remove NOT NULL from AddColumn */
-                                               def->is_not_null = false;
-
-                                               /* Add as a separate AlterTableCmd */
-                                               newcmd = makeNode(AlterTableCmd);
-                                               newcmd->subtype = AT_SetNotNull;
-                                               newcmd->name = pstrdup(def->colname);
-                                               newcmds = lappend(newcmds, newcmd);
-                                       }
-
-                                       /*
-                                        * All constraints are processed in other ways. Remove the
-                                        * original list
-                                        */
-                                       def->constraints = NIL;
-
-                                       break;
-                               }
-                       case AT_AddConstraint:
-
-                               /*
-                                * The original AddConstraint cmd node doesn't go to newcmds
-                                */
-
-                               if (IsA(cmd->def, Constraint))
-                                       transformTableConstraint(pstate, &cxt,
-                                                                                        (Constraint *) cmd->def);
-                               else if (IsA(cmd->def, FkConstraint))
-                               {
-                                       cxt.fkconstraints = lappend(cxt.fkconstraints, cmd->def);
-                                       skipValidation = false;
-                               }
-                               else
-                                       elog(ERROR, "unrecognized node type: %d",
-                                                (int) nodeTag(cmd->def));
-                               break;
+                                                RelationGetRelationName(pstate->p_target_relation)),
+                                        parser_errposition(pstate, origTarget->location)));
 
-                       case AT_ProcessedConstraint:
+               updateTargetListEntry(pstate, tle, origTarget->name,
+                                                         attrno,
+                                                         origTarget->indirection,
+                                                         origTarget->location);
 
-                               /*
-                                * Already-transformed ADD CONSTRAINT, so just make it look
-                                * like the standard case.
-                                */
-                               cmd->subtype = AT_AddConstraint;
-                               newcmds = lappend(newcmds, cmd);
-                               break;
+               /* Mark the target column as requiring update permissions */
+               target_rte->modifiedCols = bms_add_member(target_rte->modifiedCols,
+                                                               attrno - FirstLowInvalidHeapAttributeNumber);
 
-                       default:
-                               newcmds = lappend(newcmds, cmd);
-                               break;
-               }
+               origTargetList = lnext(origTargetList);
        }
+       if (origTargetList != NULL)
+               elog(ERROR, "UPDATE target count mismatch --- internal error");
+
+       return qry;
+}
 
-       /* Postprocess index and FK constraints */
-       transformIndexConstraints(pstate, &cxt);
+/*
+ * transformReturningList -
+ *     handle a RETURNING clause in INSERT/UPDATE/DELETE
+ */
+static List *
+transformReturningList(ParseState *pstate, List *returningList)
+{
+       List       *rlist;
+       int                     save_next_resno;
+       bool            save_hasAggs;
+       bool            save_hasWindowFuncs;
+       int                     length_rtable;
 
-       transformFKConstraints(pstate, &cxt, skipValidation, true);
+       if (returningList == NIL)
+               return NIL;                             /* nothing to do */
 
        /*
-        * Push any index-creation commands into the ALTER, so that they can be
-        * scheduled nicely by tablecmds.c.
+        * We need to assign resnos starting at one in the RETURNING list. Save
+        * and restore the main tlist's value of p_next_resno, just in case
+        * someone looks at it later (probably won't happen).
         */
-       foreach(l, cxt.alist)
-       {
-               Node       *idxstmt = (Node *) lfirst(l);
+       save_next_resno = pstate->p_next_resno;
+       pstate->p_next_resno = 1;
 
-               Assert(IsA(idxstmt, IndexStmt));
-               newcmd = makeNode(AlterTableCmd);
-               newcmd->subtype = AT_AddIndex;
-               newcmd->def = idxstmt;
-               newcmds = lappend(newcmds, newcmd);
-       }
-       cxt.alist = 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);
 
-       /* Append any CHECK or FK constraints to the commands list */
-       foreach(l, cxt.ckconstraints)
-       {
-               newcmd = makeNode(AlterTableCmd);
-               newcmd->subtype = AT_AddConstraint;
-               newcmd->def = (Node *) lfirst(l);
-               newcmds = lappend(newcmds, newcmd);
-       }
-       foreach(l, cxt.fkconstraints)
+       /* transform RETURNING identically to a SELECT targetlist */
+       rlist = transformTargetList(pstate, returningList);
+
+       /* check for disallowed stuff */
+
+       /* aggregates not allowed (but subselects are okay) */
+       if (pstate->p_hasAggs)
+               ereport(ERROR,
+                               (errcode(ERRCODE_GROUPING_ERROR),
+                                errmsg("cannot use aggregate function in RETURNING"),
+                                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)
        {
-               newcmd = makeNode(AlterTableCmd);
-               newcmd->subtype = AT_AddConstraint;
-               newcmd->def = (Node *) lfirst(l);
-               newcmds = lappend(newcmds, newcmd);
-       }
+               int                     vlocation = -1;
+               int                     relid;
 
-       /* Update statement's commands list */
-       stmt->cmds = newcmds;
+               /* 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"),
+                                parser_errposition(pstate, vlocation)));
+       }
 
-       qry = makeNode(Query);
-       qry->commandType = CMD_UTILITY;
-       qry->utilityStmt = (Node *) stmt;
+       /* mark column origins */
+       markTargetListOrigins(pstate, rlist);
 
-       *extras_before = list_concat(*extras_before, cxt.blist);
-       *extras_after = list_concat(cxt.alist, *extras_after);
+       /* restore state */
+       pstate->p_next_resno = save_next_resno;
+       pstate->p_hasAggs = save_hasAggs;
+       pstate->p_hasWindowFuncs = save_hasWindowFuncs;
 
-       return qry;
+       return rlist;
 }
 
+
+/*
+ * transformDeclareCursorStmt -
+ *     transform a DECLARE CURSOR Statement
+ *
+ * DECLARE CURSOR is a hybrid case: it's an optimizable statement (in fact not
+ * significantly different from a SELECT) as far as parsing/rewriting/planning
+ * are concerned, but it's not passed to the executor and so in that sense is
+ * a utility statement.  We transform it into a Query exactly as if it were
+ * a SELECT, then stick the original DeclareCursorStmt into the utilityStmt
+ * field to carry the cursor name and options.
+ */
 static Query *
 transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
 {
-       Query      *result = makeNode(Query);
-       List       *extras_before = NIL,
-                          *extras_after = NIL;
-
-       result->commandType = CMD_UTILITY;
-       result->utilityStmt = (Node *) stmt;
+       Query      *result;
 
        /*
         * Don't allow both SCROLL and NO SCROLL to be specified
@@ -2587,237 +1994,142 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
                                (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
                                 errmsg("cannot specify both SCROLL and NO SCROLL")));
 
-       stmt->query = (Node *) transformStmt(pstate, stmt->query,
-                                                                                &extras_before, &extras_after);
-
-       /* Shouldn't get any extras, since grammar only allows SelectStmt */
-       if (extras_before || extras_after)
-               elog(ERROR, "unexpected extra stuff in cursor statement");
-
-       return result;
-}
-
-
-static Query *
-transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt)
-{
-       Query      *result = makeNode(Query);
-       List       *argtype_oids;               /* argtype OIDs in a list */
-       Oid                *argtoids = NULL;    /* and as an array */
-       int                     nargs;
-       List       *queries;
-       int                     i;
-
-       result->commandType = CMD_UTILITY;
-       result->utilityStmt = (Node *) stmt;
-
-       /* Transform list of TypeNames to list (and array) of type OIDs */
-       nargs = list_length(stmt->argtypes);
-
-       if (nargs)
-       {
-               ListCell   *l;
-
-               argtoids = (Oid *) palloc(nargs * sizeof(Oid));
-               i = 0;
+       result = transformStmt(pstate, stmt->query);
 
-               foreach(l, stmt->argtypes)
-               {
-                       TypeName   *tn = lfirst(l);
-                       Oid                     toid = typenameTypeId(pstate, tn);
+       /* 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 DECLARE CURSOR");
 
-                       argtoids[i++] = toid;
-               }
-       }
+       /* 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"),
+                                parser_errposition(pstate,
+                                                               exprLocation((Node *) result->intoClause))));
 
-       /*
-        * Analyze the statement using these parameter types (any
-        * parameters passed in from above us will not be visible to it),
-        * allowing information about unknown parameters to be deduced
-        * from context.
-        */
-       queries = parse_analyze_varparams((Node *) stmt->query,
-                                                                         pstate->p_sourcetext,
-                                                                         &argtoids, &nargs);
+       /* FOR UPDATE and WITH HOLD are not compatible */
+       if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_HOLD))
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("DECLARE CURSOR WITH HOLD ... FOR UPDATE/SHARE is not supported"),
+                                errdetail("Holdable cursors must be READ ONLY.")));
 
-       /*
-        * Shouldn't get any extra statements, since grammar only allows
-        * OptimizableStmt
-        */
-       if (list_length(queries) != 1)
-               elog(ERROR, "unexpected extra stuff in prepared statement");
+       /* 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.")));
 
-       /*
-        * Check that all parameter types were determined, and convert the
-        * array of OIDs into a list for storage.
-        */
-       argtype_oids = NIL;
-       for (i = 0; i < nargs; i++)
-       {
-               Oid                     argtype = argtoids[i];
+       /* 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.")));
 
-               if (argtype == InvalidOid || argtype == UNKNOWNOID)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_INDETERMINATE_DATATYPE),
-                                        errmsg("could not determine data type of parameter $%d",
-                                                       i + 1)));
+       /* We won't need the raw querytree any more */
+       stmt->query = NULL;
 
-               argtype_oids = lappend_oid(argtype_oids, argtype);
-       }
+       result->utilityStmt = (Node *) stmt;
 
-       stmt->argtype_oids = argtype_oids;
-       stmt->query = linitial(queries);
        return result;
 }
 
+
+/*
+ * transformExplainStmt -
+ *     transform an EXPLAIN Statement
+ *
+ * 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 *
-transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt)
+transformExplainStmt(ParseState *pstate, ExplainStmt *stmt)
 {
-       Query      *result = makeNode(Query);
-       List       *paramtypes;
+       Query      *result;
 
+       /* transform contained query */
+       stmt->query = (Node *) transformStmt(pstate, stmt->query);
+
+       /* represent the command as a utility Query */
+       result = makeNode(Query);
        result->commandType = CMD_UTILITY;
        result->utilityStmt = (Node *) stmt;
 
-       paramtypes = FetchPreparedStatementParams(stmt->name);
-
-       stmt->into_has_oids = interpretOidsOption(stmt->into_contains_oids);
-
-       if (stmt->params || paramtypes)
-       {
-               int                     nparams = list_length(stmt->params);
-               int                     nexpected = list_length(paramtypes);
-               ListCell   *l,
-                                  *l2;
-               int                     i = 1;
-
-               if (nparams != nexpected)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_SYNTAX_ERROR),
-                       errmsg("wrong number of parameters for prepared statement \"%s\"",
-                                  stmt->name),
-                                        errdetail("Expected %d parameters but got %d.",
-                                                          nexpected, nparams)));
-
-               forboth(l, stmt->params, l2, paramtypes)
-               {
-                       Node       *expr = lfirst(l);
-                       Oid                     expected_type_id = lfirst_oid(l2);
-                       Oid                     given_type_id;
-
-                       expr = transformExpr(pstate, expr);
-
-                       /* Cannot contain subselects or aggregates */
-                       if (pstate->p_hasSubLinks)
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                errmsg("cannot use subquery in EXECUTE parameter")));
-                       if (pstate->p_hasAggs)
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_GROUPING_ERROR),
-                                                errmsg("cannot use aggregate function in EXECUTE parameter")));
-
-                       given_type_id = exprType(expr);
-
-                       expr = coerce_to_target_type(pstate, expr, given_type_id,
-                                                                                expected_type_id, -1,
-                                                                                COERCION_ASSIGNMENT,
-                                                                                COERCE_IMPLICIT_CAST);
-
-                       if (expr == NULL)
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                                errmsg("parameter $%d of type %s cannot be coerced to the expected type %s",
-                                                               i,
-                                                               format_type_be(given_type_id),
-                                                               format_type_be(expected_type_id)),
-                               errhint("You will need to rewrite or cast the expression.")));
-
-                       lfirst(l) = expr;
-                       i++;
-               }
-       }
-
        return result;
 }
 
-/* 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, bool forUpdate)
+CheckSelectLocking(Query *qry)
 {
-       const char *operation;
-
-       if (forUpdate)
-               operation = "SELECT FOR UPDATE";
-       else
-               operation = "SELECT FOR SHARE";
-
        if (qry->setOperations)
                ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-               /* translator: %s is a SQL command, like SELECT FOR UPDATE */
-               errmsg("%s is not allowed with UNION/INTERSECT/EXCEPT", operation)));
+                                errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
        if (qry->distinctClause != NIL)
                ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-               /* translator: %s is a SQL command, like SELECT FOR UPDATE */
-                          errmsg("%s is not allowed with DISTINCT clause", operation)));
+                                errmsg("SELECT FOR UPDATE/SHARE is not allowed with DISTINCT clause")));
        if (qry->groupClause != NIL)
                ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-               /* translator: %s is a SQL command, like SELECT FOR UPDATE */
-                          errmsg("%s is not allowed with GROUP BY clause", operation)));
+                                errmsg("SELECT FOR UPDATE/SHARE is not allowed with GROUP BY clause")));
        if (qry->havingQual != NULL)
                ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-               /* translator: %s is a SQL command, like SELECT FOR UPDATE */
-                                errmsg("%s is not allowed with HAVING clause", operation)));
+               errmsg("SELECT FOR UPDATE/SHARE is not allowed with HAVING clause")));
        if (qry->hasAggs)
                ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-               /* translator: %s is a SQL command, like SELECT FOR UPDATE */
-                  errmsg("%s is not allowed with aggregate functions", operation)));
+                                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")));
 }
 
 /*
- * Convert FOR UPDATE/SHARE name list into rowMarks list of integer relids
+ * Transform a FOR UPDATE/SHARE clause
+ *
+ * This basically involves replacing names by integer relids.
  *
  * NB: if you need to change this, see also markQueryForLocking()
- * in rewriteHandler.c.
+ * 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;
-       List       *rowMarks;
        ListCell   *l;
        ListCell   *rt;
        Index           i;
        LockingClause *allrels;
 
-       if (qry->rowMarks)
-       {
-               if (lc->forUpdate != qry->forUpdate)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                       errmsg("cannot use both FOR UPDATE and FOR SHARE in one query")));
-               if (lc->nowait != qry->rowNoWait)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                        errmsg("cannot use both wait and NOWAIT in one query")));
-       }
-       qry->forUpdate = lc->forUpdate;
-       qry->rowNoWait = lc->nowait;
-
-       CheckSelectLocking(qry, lc->forUpdate);
+       CheckSelectLocking(qry);
 
        /* make a clause we can pass down to subqueries to select all rels */
        allrels = makeNode(LockingClause);
        allrels->lockedRels = NIL;      /* indicates all rels */
        allrels->forUpdate = lc->forUpdate;
-       allrels->nowait = lc->nowait;
-
-       rowMarks = qry->rowMarks;
+       allrels->noWait = lc->noWait;
 
        if (lockedRels == NIL)
        {
@@ -2831,20 +2143,26 @@ transformLockingClause(Query *qry, LockingClause *lc)
                        switch (rte->rtekind)
                        {
                                case RTE_RELATION:
-                                       /* use list_append_unique to avoid duplicates */
-                                       rowMarks = list_append_unique_int(rowMarks, i);
+                                       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;
                        }
                }
@@ -2854,7 +2172,14 @@ transformLockingClause(Query *qry, LockingClause *lc)
                /* 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)
@@ -2862,37 +2187,53 @@ transformLockingClause(Query *qry, LockingClause *lc)
                                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:
-                                                       /* use list_append_unique to avoid duplicates */
-                                                       rowMarks = list_append_unique_int(rowMarks, i);
+                                                       applyLockingClause(qry, i,
+                                                                                          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"),
+                                                        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",
@@ -2906,330 +2247,52 @@ transformLockingClause(Query *qry, LockingClause *lc)
                                ereport(ERROR,
                                                (errcode(ERRCODE_UNDEFINED_TABLE),
                                                 errmsg("relation \"%s\" in FOR UPDATE/SHARE clause not found in FROM clause",
-                                                               relname)));
-               }
-       }
-
-       qry->rowMarks = rowMarks;
-}
-
-
-/*
- * Preprocess a list of column constraint clauses
- * to attach constraint attributes to their primary constraint nodes
- * and detect inconsistent/misplaced constraint attributes.
- *
- * NOTE: currently, attributes are only supported for FOREIGN KEY primary
- * constraints, but someday they ought to be supported for other constraints.
- */
-static void
-transformConstraintAttrs(List *constraintList)
-{
-       Node       *lastprimarynode = NULL;
-       bool            saw_deferrability = false;
-       bool            saw_initially = false;
-       ListCell   *clist;
-
-       foreach(clist, constraintList)
-       {
-               Node       *node = lfirst(clist);
-
-               if (!IsA(node, Constraint))
-               {
-                       lastprimarynode = node;
-                       /* reset flags for new primary node */
-                       saw_deferrability = false;
-                       saw_initially = false;
-               }
-               else
-               {
-                       Constraint *con = (Constraint *) node;
-
-                       switch (con->contype)
-                       {
-                               case CONSTR_ATTR_DEFERRABLE:
-                                       if (lastprimarynode == NULL ||
-                                               !IsA(lastprimarynode, FkConstraint))
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                                                errmsg("misplaced DEFERRABLE clause")));
-                                       if (saw_deferrability)
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                                                errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
-                                       saw_deferrability = true;
-                                       ((FkConstraint *) lastprimarynode)->deferrable = true;
-                                       break;
-                               case CONSTR_ATTR_NOT_DEFERRABLE:
-                                       if (lastprimarynode == NULL ||
-                                               !IsA(lastprimarynode, FkConstraint))
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                                                errmsg("misplaced NOT DEFERRABLE clause")));
-                                       if (saw_deferrability)
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                                                errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
-                                       saw_deferrability = true;
-                                       ((FkConstraint *) lastprimarynode)->deferrable = false;
-                                       if (saw_initially &&
-                                               ((FkConstraint *) lastprimarynode)->initdeferred)
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                                                errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE")));
-                                       break;
-                               case CONSTR_ATTR_DEFERRED:
-                                       if (lastprimarynode == NULL ||
-                                               !IsA(lastprimarynode, FkConstraint))
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                                        errmsg("misplaced INITIALLY DEFERRED clause")));
-                                       if (saw_initially)
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                                                errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
-                                       saw_initially = true;
-                                       ((FkConstraint *) lastprimarynode)->initdeferred = true;
-
-                                       /*
-                                        * If only INITIALLY DEFERRED appears, assume DEFERRABLE
-                                        */
-                                       if (!saw_deferrability)
-                                               ((FkConstraint *) lastprimarynode)->deferrable = true;
-                                       else if (!((FkConstraint *) lastprimarynode)->deferrable)
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                                                errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE")));
-                                       break;
-                               case CONSTR_ATTR_IMMEDIATE:
-                                       if (lastprimarynode == NULL ||
-                                               !IsA(lastprimarynode, FkConstraint))
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                                       errmsg("misplaced INITIALLY IMMEDIATE clause")));
-                                       if (saw_initially)
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                                                errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
-                                       saw_initially = true;
-                                       ((FkConstraint *) lastprimarynode)->initdeferred = false;
-                                       break;
-                               default:
-                                       /* Otherwise it's not an attribute */
-                                       lastprimarynode = node;
-                                       /* reset flags for new primary node */
-                                       saw_deferrability = false;
-                                       saw_initially = false;
-                                       break;
-                       }
+                                                               thisrel->relname),
+                                                parser_errposition(pstate, thisrel->location)));
                }
        }
 }
 
-/* Build a FromExpr node */
-static FromExpr *
-makeFromExpr(List *fromlist, Node *quals)
-{
-       FromExpr   *f = makeNode(FromExpr);
-
-       f->fromlist = fromlist;
-       f->quals = quals;
-       return f;
-}
-
 /*
- * Special handling of type definition for a column
+ * Record locking info for a single rangetable item
  */
-static void
-transformColumnType(ParseState *pstate, ColumnDef *column)
-{
-       /*
-        * All we really need to do here is verify that the type is valid.
-        */
-       Type            ctype = typenameType(pstate, column->typename);
-
-       ReleaseSysCache(ctype);
-}
-
-static void
-setSchemaName(char *context_schema, char **stmt_schema_name)
+void
+applyLockingClause(Query *qry, Index rtindex,
+                                  bool forUpdate, bool noWait, bool pushedDown)
 {
-       if (*stmt_schema_name == NULL)
-               *stmt_schema_name = context_schema;
-       else if (strcmp(context_schema, *stmt_schema_name) != 0)
-               ereport(ERROR,
-                               (errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
-                                errmsg("CREATE specifies a schema (%s) "
-                                               "different from the one being created (%s)",
-                                               *stmt_schema_name, context_schema)));
-}
+       RowMarkClause *rc;
 
-/*
- * analyzeCreateSchemaStmt -
- *       analyzes the "create schema" statement
- *
- * Split the schema element list into individual commands and place
- * them in the result list in an order such that there are no forward
- * references (e.g. GRANT to a table created later in the list). Note
- * that the logic we use for determining forward references is
- * presently quite incomplete.
- *
- * SQL92 also allows constraints to make forward references, so thumb through
- * the table columns and move forward references to a posterior alter-table
- * command.
- *
- * The result is a list of parse nodes that still need to be analyzed ---
- * but we can't analyze the later commands until we've executed the earlier
- * ones, because of possible inter-object references.
- *
- * Note: Called from commands/schemacmds.c
- */
-List *
-analyzeCreateSchemaStmt(CreateSchemaStmt *stmt)
-{
-       CreateSchemaStmtContext cxt;
-       List       *result;
-       ListCell   *elements;
-
-       cxt.stmtType = "CREATE SCHEMA";
-       cxt.schemaname = stmt->schemaname;
-       cxt.authid = stmt->authid;
-       cxt.sequences = NIL;
-       cxt.tables = NIL;
-       cxt.views = NIL;
-       cxt.indexes = NIL;
-       cxt.grants = NIL;
-       cxt.triggers = NIL;
-       cxt.fwconstraints = NIL;
-       cxt.alters = NIL;
-       cxt.blist = NIL;
-       cxt.alist = NIL;
+       /* If it's an explicit clause, make sure hasForUpdate gets set */
+       if (!pushedDown)
+               qry->hasForUpdate = true;
 
-       /*
-        * Run through each schema element in the schema element list. Separate
-        * statements by type, and do preliminary analysis.
-        */
-       foreach(elements, stmt->schemaElts)
+       /* Check for pre-existing entry for same rtindex */
+       if ((rc = get_parse_rowmark(qry, rtindex)) != NULL)
        {
-               Node       *element = lfirst(elements);
-
-               switch (nodeTag(element))
-               {
-                       case T_CreateSeqStmt:
-                               {
-                                       CreateSeqStmt *elp = (CreateSeqStmt *) element;
-
-                                       setSchemaName(cxt.schemaname, &elp->sequence->schemaname);
-                                       cxt.sequences = lappend(cxt.sequences, element);
-                               }
-                               break;
-
-                       case T_CreateStmt:
-                               {
-                                       CreateStmt *elp = (CreateStmt *) element;
-
-                                       setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-
-                                       /*
-                                        * XXX todo: deal with constraints
-                                        */
-                                       cxt.tables = lappend(cxt.tables, element);
-                               }
-                               break;
-
-                       case T_ViewStmt:
-                               {
-                                       ViewStmt   *elp = (ViewStmt *) element;
-
-                                       setSchemaName(cxt.schemaname, &elp->view->schemaname);
-
-                                       /*
-                                        * XXX todo: deal with references between views
-                                        */
-                                       cxt.views = lappend(cxt.views, element);
-                               }
-                               break;
-
-                       case T_IndexStmt:
-                               {
-                                       IndexStmt  *elp = (IndexStmt *) element;
-
-                                       setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-                                       cxt.indexes = lappend(cxt.indexes, element);
-                               }
-                               break;
-
-                       case T_CreateTrigStmt:
-                               {
-                                       CreateTrigStmt *elp = (CreateTrigStmt *) element;
-
-                                       setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-                                       cxt.triggers = lappend(cxt.triggers, element);
-                               }
-                               break;
-
-                       case T_GrantStmt:
-                               cxt.grants = lappend(cxt.grants, element);
-                               break;
-
-                       default:
-                               elog(ERROR, "unrecognized node type: %d",
-                                        (int) nodeTag(element));
-               }
+               /*
+                * If the same RTE is specified both FOR UPDATE and FOR SHARE, treat
+                * it as FOR UPDATE.  (Reasonable, since you can't take both a shared
+                * and exclusive lock at the same time; it'll end up being exclusive
+                * anyway.)
+                *
+                * We also consider that NOWAIT wins if it's specified both ways. This
+                * is a bit more debatable but raising an error doesn't seem helpful.
+                * (Consider for instance SELECT FOR UPDATE NOWAIT from a view that
+                * internally contains a plain FOR UPDATE spec.)
+                *
+                * And of course pushedDown becomes false if any clause is explicit.
+                */
+               rc->forUpdate |= forUpdate;
+               rc->noWait |= noWait;
+               rc->pushedDown &= pushedDown;
+               return;
        }
 
-       result = NIL;
-       result = list_concat(result, cxt.sequences);
-       result = list_concat(result, cxt.tables);
-       result = list_concat(result, cxt.views);
-       result = list_concat(result, cxt.indexes);
-       result = list_concat(result, cxt.triggers);
-       result = list_concat(result, cxt.grants);
-
-       return result;
-}
-
-/*
- * 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);
+       /* Make a new RowMarkClause */
+       rc = makeNode(RowMarkClause);
+       rc->rti = rtindex;
+       rc->forUpdate = forUpdate;
+       rc->noWait = noWait;
+       rc->pushedDown = pushedDown;
+       qry->rowMarks = lappend(qry->rowMarks, rc);
 }