/*-------------------------------------------------------------------------
*
* 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.338 2006/07/03 22:45:39 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 "commands/tablecmds.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;
}
/*
* 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:
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;
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;
*/
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);
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;
}
* 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
* 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;
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
{
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
* see.
*/
ParseState *sub_pstate = make_parsestate(pstate);
- RangeTblEntry *rte;
- RangeTblRef *rtr;
+ Query *selectQuery;
/*
* Process the source SELECT.
* 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
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.)
*
* 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)
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->options);
+ 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);
+ /*
+ * 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))))));
+ }
- switch (nodeTag(element))
- {
- case T_ColumnDef:
- transformColumnDefinition(pstate, &cxt,
- (ColumnDef *) element);
- break;
+ /*
+ * Prepare columns for assignment to target table.
+ */
+ result = NIL;
+ icols = list_head(icolumns);
+ attnos = list_head(attrnos);
+ foreach(lc, exprlist)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+ ResTarget *col;
- case T_Constraint:
- transformTableConstraint(pstate, &cxt,
- (Constraint *) element);
- break;
+ col = (ResTarget *) lfirst(icols);
+ Assert(IsA(col, ResTarget));
- case T_FkConstraint:
- /* No pre-transformation needed */
- cxt.fkconstraints = lappend(cxt.fkconstraints, element);
- break;
+ expr = transformAssignedExpr(pstate, expr,
+ col->name,
+ lfirst_int(attnos),
+ col->indirection,
+ col->location);
- case T_InhRelation:
- transformInhRelation(pstate, &cxt,
- (InhRelation *) element);
- break;
+ result = lappend(result, expr);
- default:
- elog(ERROR, "unrecognized node type: %d",
- (int) nodeTag(element));
- break;
- }
+ icols = lnext(icols);
+ attnos = lnext(attnos);
}
- Assert(stmt->constraints == NIL);
-
- /*
- * Postprocess constraints that give rise to index definitions.
- */
- 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;
+ return result;
}
-static void
-transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt,
- ColumnDef *column)
+/*
+ * 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)
{
- 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)
- {
- 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)));
-
- /*
- * 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;
-
- cxt->blist = lappend(cxt->blist, seqstmt);
-
- /*
- * Mark the ColumnDef so that during execution, an appropriate
- * dependency will be added from the sequence to the column.
- */
- column->support = makeRangeVar(snamespace, sname);
-
- /*
- * 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);
- }
-
- /* Process column constraints, if any... */
- transformConstraintAttrs(column->constraints);
-
- saw_nullable = false;
-
- foreach(clist, column->constraints)
+ if (expr == NULL)
+ return -1;
+ if (IsA(expr, RowExpr))
+ return list_length(((RowExpr *) expr)->args);
+ if (IsA(expr, Var))
{
- constraint = lfirst(clist);
-
- /*
- * 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))
- {
- FkConstraint *fkconstraint = (FkConstraint *) constraint;
-
- fkconstraint->fk_attrs = list_make1(makeString(column->colname));
- cxt->fkconstraints = lappend(cxt->fkconstraints, fkconstraint);
- continue;
- }
-
- Assert(IsA(constraint, Constraint));
+ Var *var = (Var *) expr;
+ AttrNumber attnum = var->varattno;
- switch (constraint->contype)
+ if (attnum > 0 && var->vartype == RECORDOID)
{
- 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;
+ RangeTblEntry *rte;
- 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;
-
- 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;
-
- bool including_defaults = false;
- bool including_constraints = false;
- bool including_indexes = false;
- ListCell *elem;
-
- 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));
-
- tupleDesc = RelationGetDescr(relation);
- constr = tupleDesc->constr;
+ Query *qry = makeNode(Query);
+ Node *qual;
+ ListCell *l;
- foreach(elem, inhRelation->options)
- {
- int option = lfirst_int(elem);
- switch (option)
- {
- case CREATE_TABLE_LIKE_INCLUDING_DEFAULTS:
- including_defaults = true;
- break;
- case CREATE_TABLE_LIKE_EXCLUDING_DEFAULTS:
- including_defaults = false;
- break;
- case CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS:
- including_constraints = true;
- break;
- case CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS:
- including_constraints = false;
- break;
- case CREATE_TABLE_LIKE_INCLUDING_INDEXES:
- including_indexes = true;
- break;
- case CREATE_TABLE_LIKE_EXCLUDING_INDEXES:
- including_indexes = false;
- break;
- default:
- elog(ERROR, "unrecognized CREATE TABLE LIKE option: %d", option);
- }
- }
+ qry->commandType = CMD_SELECT;
- if (including_indexes)
- elog(ERROR, "TODO");
-
- /*
- * 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 = true;
- 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 && 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);
- }
- }
-
- if (including_constraints && tupleDesc->constr) {
- int ccnum;
- AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
-
- for(ccnum = 0; ccnum < tupleDesc->constr->num_check; ccnum++) {
- char *ccname = tupleDesc->constr->check[ccnum].ccname;
- char *ccbin = tupleDesc->constr->check[ccnum].ccbin;
- Node *ccbin_node = stringToNode(ccbin);
- Constraint *n = makeNode(Constraint);
-
- change_varattnos_of_a_node(ccbin_node, attmap);
-
- n->contype = CONSTR_CHECK;
- n->name = pstrdup(ccname);
- n->raw_expr = ccbin_node;
- n->cooked_expr = NULL;
- n->indexspace = NULL;
- cxt->ckconstraints = lappend(cxt->ckconstraints, (Node*)n);
- }
- }
+ /* 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->options = constraint->options;
- 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);
- }
+ qry->sortClause = transformSortClause(pstate,
+ stmt->sortClause,
+ &qry->targetList,
+ true /* fix unknowns */ ,
+ false /* allow SQL92 rules */ );
- indexlist = lappend(indexlist, index);
- }
+ qry->groupClause = transformGroupClause(pstate,
+ stmt->groupClause,
+ &qry->targetList,
+ qry->sortClause,
+ 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)
+ if (stmt->distinctClause == NIL)
{
- /* Make sure we keep the PKEY index in preference to others... */
- cxt->alist = list_make1(cxt->pkey);
+ qry->distinctClause = NIL;
+ qry->hasDistinctOn = false;
}
-
- foreach(l, indexlist)
+ else if (linitial(stmt->distinctClause) == NULL)
{
- 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);
+ /* We had SELECT DISTINCT */
+ qry->distinctClause = transformDistinctClause(pstate,
+ &qry->targetList,
+ qry->sortClause,
+ false);
+ 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)
- {
- foreach(fkclist, cxt->fkconstraints)
- {
- FkConstraint *fkconstraint = (FkConstraint *) lfirst(fkclist);
-
- fkconstraint->skip_validation = true;
- }
- }
-
- /*
- * 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("cannot use aggregate function in rule WHERE condition")));
-
- /* 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;
-}
-
-
-/*
- * 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;
- ListCell *l;
+ /* Do basic expression transformation (same as a ROW() expr) */
+ sublist = transformExpressionList(pstate, sublist);
- qry->commandType = CMD_SELECT;
+ /*
+ * 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))));
+ }
- /* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
- pstate->p_locking_clause = stmt->lockingClause;
+ exprsLists = lappend(exprsLists, sublist);
- /* process the FROM clause */
- transformFromClause(pstate, stmt->fromClause);
+ /* Check for DEFAULT and build per-column expression lists */
+ i = 0;
+ foreach(lc2, sublist)
+ {
+ Node *col = (Node *) lfirst(lc2);
- /* transform targetlist */
- qry->targetList = transformTargetList(pstate, stmt->targetList);
+ 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++;
+ }
+ }
- /* handle any SELECT INTO/CREATE TABLE AS spec */
- qry->into = stmt->into;
- if (stmt->intoColNames)
- applyColumnNames(qry->targetList, stmt->intoColNames);
+ /*
+ * 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);
+ }
- qry->intoOptions = copyObject(stmt->intoOptions);
- qry->intoOnCommit = stmt->intoOnCommit;
- qry->intoTableSpaceName = stmt->intoTableSpaceName;
+ newExprsLists = NIL;
+ foreach(lc, exprsLists)
+ {
+ List *sublist = (List *) lfirst(lc);
+ List *newsublist = NIL;
- /* mark column origins */
- markTargetListOrigins(pstate, qry->targetList);
+ i = 0;
+ foreach(lc2, sublist)
+ {
+ Node *col = (Node *) lfirst(lc2);
- /* transform WHERE */
- qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
+ col = coerce_to_common_type(pstate, col, coltypes[i], "VALUES");
+ newsublist = lappend(newsublist, col);
+ i++;
+ }
+
+ 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");
- qry->rtable = pstate->p_rtable;
- qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
-
- qry->hasSubLinks = pstate->p_hasSubLinks;
- qry->hasAggs = pstate->p_hasAggs;
- if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
- parseCheckAggregates(pstate, qry);
+ if (stmt->lockingClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES")));
- foreach(l, stmt->lockingClause)
+ /* handle any CREATE TABLE AS spec */
+ if (stmt->intoClause)
{
- transformLockingClause(qry, (LockingClause *) lfirst(l));
+ 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, NULL);
+
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ /* 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
int leftmostRTI;
Query *leftmostQuery;
SetOperationStmt *sostmt;
- RangeVar *into;
- List *intoColNames;
+ List *socolinfo;
+ List *intoColNames = NIL;
List *sortClause;
Node *limitOffset;
Node *limitCount;
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.
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
/*
* 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;
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
* "ORDER BY upper(foo)" will draw the right error message rather than
* "foo not found".
*/
- jrte = addRangeTableEntryForJoin(NULL,
+ sv_rtable_length = list_length(pstate->p_rtable);
+
+ jrte = addRangeTableEntryForJoin(pstate,
targetnames,
JOIN_INNER,
targetvars,
NULL,
false);
- sv_rtable = pstate->p_rtable;
- pstate->p_rtable = list_make1(jrte);
-
sv_relnamespace = pstate->p_relnamespace;
pstate->p_relnamespace = NIL; /* no qualified names allowed */
qry->sortClause = transformSortClause(pstate,
sortClause,
&qry->targetList,
- false /* no unknowns expected */ );
+ false /* no unknowns expected */ ,
+ false /* allow SQL92 rules */ );
- pstate->p_rtable = sv_rtable;
+ pstate->p_rtable = list_truncate(pstate->p_rtable, sv_rtable_length);
pstate->p_relnamespace = sv_relnamespace;
pstate->p_varnamespace = sv_varnamespace;
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);
qry->hasAggs = pstate->p_hasAggs;
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
parseCheckAggregates(pstate, qry);
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ if (pstate->p_hasWindowFuncs)
+ parseCheckWindowFuncs(pstate, qry);
foreach(l, lockingClause)
{
- transformLockingClause(qry, (LockingClause *) lfirst(l));
+ transformLockingClause(pstate, qry,
+ (LockingClause *) lfirst(l), false);
}
return qry;
/*
* transformSetOperationTree
* Recursively transform leaves and internal nodes of a set-op tree
+ *
+ * In addition to returning the transformed node, we return a list of
+ * expression nodes showing the type, typmod, and location (for error messages)
+ * of each output column of the set-op node. This is used only during the
+ * internal recursion of this function. At the upper levels we use
+ * SetToDefault nodes for this purpose, since they carry exactly the fields
+ * needed, but any other expression node type would do as well.
*/
static Node *
-transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
+transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
+ bool isTopLevel, List **colInfo)
{
bool isLeaf;
/*
* 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,
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.
if (isLeaf)
{
/* Process leaf SELECT */
- List *selectList;
Query *selectQuery;
char selectName[32];
RangeTblEntry *rte;
RangeTblRef *rtr;
+ ListCell *tl;
/*
* Transform SelectStmt into a Query.
* of this sub-query, because they are not in the toplevel pstate's
* namespace list.
*/
- 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
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);
}
/*
{
/* 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" :
op->all = stmt->all;
/*
- * Recursively transform the child nodes.
+ * Recursively transform the left child node.
+ */
+ op->larg = transformSetOperationTree(pstate, stmt->larg,
+ false,
+ &lcolinfo);
+
+ /*
+ * If we are processing a recursive union query, now is the time to
+ * examine the non-recursive term's output columns and mark the
+ * containing CTE as having those result columns. We should do this
+ * only at the topmost setop of the CTE, of course.
+ */
+ if (isTopLevel &&
+ pstate->p_parent_cte &&
+ pstate->p_parent_cte->cterecursive)
+ determineRecursiveColTypes(pstate, op->larg, lcolinfo);
+
+ /*
+ * Recursively transform the right child node.
*/
- op->larg = transformSetOperationTree(pstate, stmt->larg);
- op->rarg = transformSetOperationTree(pstate, stmt->rarg);
+ op->rarg = transformSetOperationTree(pstate, stmt->rarg,
+ false,
+ &rcolinfo);
/*
* Verify that the two children have the same number of non-junk
* columns, and determine the types of the merged output columns.
*/
- 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;
}
/*
- * 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")));
}
transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
{
Query *qry = makeNode(Query);
+ RangeTblEntry *target_rte;
Node *qual;
ListCell *origTargetList;
ListCell *tl;
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;
/*
- * 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.)
+ * 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)
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
- errmsg("cannot use aggregate function in UPDATE")));
+ errmsg("cannot use aggregate function in UPDATE"),
+ parser_errposition(pstate,
+ locate_agg_of_level((Node *) qry, 0))));
+ if (pstate->p_hasWindowFuncs)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("cannot use window function in UPDATE"),
+ parser_errposition(pstate,
+ locate_windowfunc((Node *) qry))));
/*
* Now we are done with SELECT-like processing, and can get on with
pstate->p_next_resno = pstate->p_target_relation->rd_rel->relnatts + 1;
/* Prepare non-junk columns for assignment to target table */
+ target_rte = pstate->p_target_rangetblentry;
origTargetList = list_head(stmt->targetList);
foreach(tl, qry->targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(tl);
ResTarget *origTarget;
- int attrno;
+ int attrno;
if (tle->resjunk)
{
origTarget = (ResTarget *) lfirst(origTargetList);
Assert(IsA(origTarget, ResTarget));
- attrno = attnameAttNum(pstate->p_target_relation,
- origTarget->name, true);
- if (attrno == InvalidAttrNumber)
- ereport(ERROR,
- (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;
-
- case AT_ProcessedConstraint:
+ attrno = attnameAttNum(pstate->p_target_relation,
+ origTarget->name, true);
+ if (attrno == InvalidAttrNumber)
+ ereport(ERROR,
+ (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)));
- /*
- * Already-transformed ADD CONSTRAINT, so just make it look
- * like the standard case.
- */
- cmd->subtype = AT_AddConstraint;
- newcmds = lappend(newcmds, cmd);
- break;
+ updateTargetListEntry(pstate, tle, origTarget->name,
+ attrno,
+ origTarget->indirection,
+ origTarget->location);
- default:
- newcmds = lappend(newcmds, cmd);
- break;
- }
+ /* Mark the target column as requiring update permissions */
+ target_rte->modifiedCols = bms_add_member(target_rte->modifiedCols,
+ attrno - FirstLowInvalidHeapAttributeNumber);
+
+ origTargetList = lnext(origTargetList);
}
+ if (origTargetList != NULL)
+ 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
(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);
-
- 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)
{
if (qry->havingQual != NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE is not allowed with HAVING clause")));
+ errmsg("SELECT FOR UPDATE/SHARE is not allowed with HAVING clause")));
if (qry->hasAggs)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE is not allowed with aggregate functions")));
+ if (qry->hasWindowFuncs)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SELECT FOR UPDATE/SHARE is not allowed with window functions")));
+ if (expression_returns_set((Node *) qry->targetList))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SELECT FOR UPDATE/SHARE is not allowed with set-returning functions in the target list")));
}
/*
* This basically involves replacing names by integer relids.
*
* NB: if you need to change this, see also markQueryForLocking()
- * in rewriteHandler.c.
+ * in rewriteHandler.c, and isLockedRefname() in parse_relation.c.
*/
static void
-transformLockingClause(Query *qry, LockingClause *lc)
+transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
+ bool pushedDown)
{
List *lockedRels = lc->lockedRels;
ListCell *l;
switch (rte->rtekind)
{
case RTE_RELATION:
- applyLockingClause(qry, i, lc->forUpdate, lc->noWait);
+ applyLockingClause(qry, i,
+ lc->forUpdate, lc->noWait, pushedDown);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
break;
case RTE_SUBQUERY:
+ applyLockingClause(qry, i,
+ lc->forUpdate, lc->noWait, pushedDown);
/*
* FOR UPDATE/SHARE of subquery is propagated to all of
- * subquery's rels
+ * subquery's rels, too. We could do this later (based on
+ * the marking of the subquery RTE) but it is convenient
+ * to have local knowledge in each query level about which
+ * rels need to be opened with RowShareLock.
*/
- transformLockingClause(rte->subquery, allrels);
+ transformLockingClause(pstate, rte->subquery,
+ allrels, true);
break;
default:
- /* ignore JOIN, SPECIAL, FUNCTION RTEs */
+ /* ignore JOIN, SPECIAL, FUNCTION, VALUES, CTE RTEs */
break;
}
}
/* just the named tables */
foreach(l, lockedRels)
{
- char *relname = strVal(lfirst(l));
+ RangeVar *thisrel = (RangeVar *) lfirst(l);
+
+ /* For simplicity we insist on unqualified alias names here */
+ if (thisrel->catalogname || thisrel->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("SELECT FOR UPDATE/SHARE must specify unqualified relation names"),
+ parser_errposition(pstate, thisrel->location)));
i = 0;
foreach(rt, qry->rtable)
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
++i;
- if (strcmp(rte->eref->aliasname, relname) == 0)
+ if (strcmp(rte->eref->aliasname, thisrel->relname) == 0)
{
switch (rte->rtekind)
{
case RTE_RELATION:
applyLockingClause(qry, i,
- lc->forUpdate, lc->noWait);
+ lc->forUpdate, lc->noWait,
+ pushedDown);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
break;
case RTE_SUBQUERY:
-
- /*
- * FOR UPDATE/SHARE of subquery is propagated to
- * all of subquery's rels
- */
- transformLockingClause(rte->subquery, allrels);
+ applyLockingClause(qry, i,
+ lc->forUpdate, lc->noWait,
+ pushedDown);
+ /* see comment above */
+ transformLockingClause(pstate, rte->subquery,
+ allrels, true);
break;
case RTE_JOIN:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a join")));
+ errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a join"),
+ parser_errposition(pstate, thisrel->location)));
break;
case RTE_SPECIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE cannot be applied to NEW or OLD")));
+ errmsg("SELECT FOR UPDATE/SHARE cannot be applied to NEW or OLD"),
+ parser_errposition(pstate, thisrel->location)));
break;
case RTE_FUNCTION:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a function")));
+ errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a function"),
+ parser_errposition(pstate, thisrel->location)));
+ break;
+ case RTE_VALUES:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES"),
+ parser_errposition(pstate, thisrel->location)));
+ break;
+ case RTE_CTE:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a WITH query"),
+ parser_errposition(pstate, thisrel->location)));
break;
default:
elog(ERROR, "unrecognized RTE type: %d",
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" in FOR UPDATE/SHARE clause not found in FROM clause",
- relname)));
+ thisrel->relname),
+ parser_errposition(pstate, thisrel->location)));
}
}
}
* Record locking info for a single rangetable item
*/
void
-applyLockingClause(Query *qry, Index rtindex, bool forUpdate, bool noWait)
+applyLockingClause(Query *qry, Index rtindex,
+ bool forUpdate, bool noWait, bool pushedDown)
{
RowMarkClause *rc;
+ /* If it's an explicit clause, make sure hasForUpdate gets set */
+ if (!pushedDown)
+ qry->hasForUpdate = true;
+
/* Check for pre-existing entry for same rtindex */
- if ((rc = get_rowmark(qry, rtindex)) != NULL)
+ if ((rc = get_parse_rowmark(qry, rtindex)) != NULL)
{
/*
- * If the same RTE is specified both FOR UPDATE and FOR SHARE,
- * treat 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.)
+ * 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.)
*
- * 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;
}
rc->rti = rtindex;
rc->forUpdate = forUpdate;
rc->noWait = noWait;
+ rc->pushedDown = pushedDown;
qry->rowMarks = lappend(qry->rowMarks, rc);
}
-
-
-/*
- * 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;
- }
- }
- }
-}
-
-/* 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
- */
-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)
-{
- 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)));
-}
-
-/*
- * 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;
-
- /*
- * Run through each schema element in the schema element list. Separate
- * statements by type, and do preliminary analysis.
- */
- foreach(elements, stmt->schemaElts)
- {
- 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));
- }
- }
-
- 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);
-}