aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2007-06-23 22:12:52 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2007-06-23 22:12:52 +0000
commit46379d6e60f0f95e127a5045ca1fa74dfdc48a85 (patch)
tree7d6fe8004575203b40a38184c154dc7f34a20345
parentec0bb02db8452d4098023f82b100ba68d8f7dfab (diff)
downloadpostgresql-46379d6e60f0f95e127a5045ca1fa74dfdc48a85.tar.gz
postgresql-46379d6e60f0f95e127a5045ca1fa74dfdc48a85.zip
Separate parse-analysis for utility commands out of parser/analyze.c
(which now deals only in optimizable statements), and put that code into a new file parser/parse_utilcmd.c. This helps clarify and enforce the design rule that utility statements shouldn't be processed during the regular parse analysis phase; all interpretation of their meaning should happen after they are given to ProcessUtility to execute. (We need this because we don't retain any locks for a utility statement that's in a plan cache, nor have any way to detect that it's stale.) We are also able to simplify the API for parse_analyze() and related routines, because they will now always return exactly one Query structure. In passing, fix bug #3403 concerning trying to add a serial column to an existing temp table (this is largely Heikki's work, but we needed all that restructuring to make it safe).
-rw-r--r--src/backend/commands/indexcmds.c4
-rw-r--r--src/backend/commands/prepare.c18
-rw-r--r--src/backend/commands/schemacmds.c50
-rw-r--r--src/backend/commands/tablecmds.c57
-rw-r--r--src/backend/commands/view.c10
-rw-r--r--src/backend/nodes/makefuncs.c16
-rw-r--r--src/backend/optimizer/util/clauses.c14
-rw-r--r--src/backend/parser/Makefile4
-rw-r--r--src/backend/parser/README11
-rw-r--r--src/backend/parser/analyze.c1928
-rw-r--r--src/backend/parser/gram.y11
-rw-r--r--src/backend/parser/parse_clause.c22
-rw-r--r--src/backend/parser/parse_expr.c10
-rw-r--r--src/backend/parser/parse_node.c35
-rw-r--r--src/backend/parser/parse_utilcmd.c1800
-rw-r--r--src/backend/rewrite/rewriteDefine.c6
-rw-r--r--src/backend/tcop/postgres.c88
-rw-r--r--src/backend/tcop/utility.c89
-rw-r--r--src/include/nodes/makefuncs.h4
-rw-r--r--src/include/nodes/parsenodes.h18
-rw-r--r--src/include/parser/analyze.h15
-rw-r--r--src/include/parser/parse_node.h3
-rw-r--r--src/include/parser/parse_utilcmd.h28
23 files changed, 2144 insertions, 2097 deletions
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 24cb898b6a0..98dad737133 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.159 2007/06/03 17:06:16 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.160 2007/06/23 22:12:50 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -367,7 +367,7 @@ DefineIndex(RangeVar *heapRelation,
/*
* This shouldn't happen during CREATE TABLE, but can happen
* during ALTER TABLE. Keep message in sync with
- * transformIndexConstraints() in parser/analyze.c.
+ * transformIndexConstraints() in parser/parse_utilcmd.c.
*/
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index de999a36376..38055997faa 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -10,7 +10,7 @@
* Copyright (c) 2002-2007, PostgreSQL Global Development Group
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.76 2007/05/25 17:54:25 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.77 2007/06/23 22:12:50 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -55,7 +55,6 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
{
Oid *argtypes = NULL;
int nargs;
- List *queries;
Query *query;
List *query_list,
*plan_list;
@@ -105,9 +104,9 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
* Because parse analysis scribbles on the raw querytree, we must make
* a copy to ensure we have a pristine raw tree to cache. FIXME someday.
*/
- queries = parse_analyze_varparams((Node *) copyObject(stmt->query),
- queryString,
- &argtypes, &nargs);
+ query = parse_analyze_varparams((Node *) copyObject(stmt->query),
+ queryString,
+ &argtypes, &nargs);
/*
* Check that all parameter types were determined.
@@ -124,15 +123,8 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
}
/*
- * Shouldn't get any extra statements, since grammar only allows
- * OptimizableStmt
+ * grammar only allows OptimizableStmt, so this check should be redundant
*/
- if (list_length(queries) != 1)
- elog(ERROR, "unexpected extra stuff in prepared statement");
-
- query = (Query *) linitial(queries);
- Assert(IsA(query, Query));
-
switch (query->commandType)
{
case CMD_SELECT:
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 5a03c7780f3..b103667935f 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/schemacmds.c,v 1.45 2007/03/23 19:53:51 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/schemacmds.c,v 1.46 2007/06/23 22:12:50 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -24,7 +24,7 @@
#include "commands/dbcommands.h"
#include "commands/schemacmds.h"
#include "miscadmin.h"
-#include "parser/analyze.h"
+#include "parser/parse_utilcmd.h"
#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -111,39 +111,31 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
/*
* Examine the list of commands embedded in the CREATE SCHEMA command, and
* reorganize them into a sequentially executable order with no forward
- * references. Note that the result is still a list of raw parsetrees in
- * need of parse analysis --- we cannot, in general, run analyze.c on one
- * statement until we have actually executed the prior ones.
+ * references. Note that the result is still a list of raw parsetrees
+ * --- we cannot, in general, run parse analysis on one statement until
+ * we have actually executed the prior ones.
*/
- parsetree_list = analyzeCreateSchemaStmt(stmt);
+ parsetree_list = transformCreateSchemaStmt(stmt);
/*
- * Analyze and execute each command contained in the CREATE SCHEMA
+ * Execute each command contained in the CREATE SCHEMA. Since the
+ * grammar allows only utility commands in CREATE SCHEMA, there is
+ * no need to pass them through parse_analyze() or the rewriter;
+ * we can just hand them straight to ProcessUtility.
*/
foreach(parsetree_item, parsetree_list)
{
- Node *parsetree = (Node *) lfirst(parsetree_item);
- List *querytree_list;
- ListCell *querytree_item;
-
- querytree_list = parse_analyze(parsetree, queryString, NULL, 0);
-
- foreach(querytree_item, querytree_list)
- {
- Query *querytree = (Query *) lfirst(querytree_item);
-
- /* schemas should contain only utility stmts */
- Assert(querytree->commandType == CMD_UTILITY);
- /* do this step */
- ProcessUtility(querytree->utilityStmt,
- queryString,
- NULL,
- false, /* not top level */
- None_Receiver,
- NULL);
- /* make sure later steps can see the object created here */
- CommandCounterIncrement();
- }
+ Node *stmt = (Node *) lfirst(parsetree_item);
+
+ /* do this step */
+ ProcessUtility(stmt,
+ queryString,
+ NULL,
+ false, /* not top level */
+ None_Receiver,
+ NULL);
+ /* make sure later steps can see the object created here */
+ CommandCounterIncrement();
}
/* Reset search path to normal state */
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b9bebde8f10..f50b59d0d8f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.227 2007/06/03 22:16:03 petere Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.228 2007/06/23 22:12:50 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -44,7 +44,6 @@
#include "optimizer/clauses.h"
#include "optimizer/plancat.h"
#include "optimizer/prep.h"
-#include "parser/analyze.h"
#include "parser/gramparse.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
@@ -52,6 +51,7 @@
#include "parser/parse_oper.h"
#include "parser/parse_relation.h"
#include "parser/parse_type.h"
+#include "parser/parse_utilcmd.h"
#include "parser/parser.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteHandler.h"
@@ -394,7 +394,7 @@ DefineRelation(CreateStmt *stmt, char relkind)
add_nonduplicate_constraint(cdef, check, &ncheck);
}
/*
- * analyze.c might have passed some precooked constraints too,
+ * parse_utilcmd.c might have passed some precooked constraints too,
* due to LIKE tab INCLUDING CONSTRAINTS
*/
foreach(listptr, stmt->constraints)
@@ -2922,7 +2922,7 @@ find_composite_type_dependencies(Oid typeOid,
*
* Adds an additional attribute to a relation making the assumption that
* CHECK, NOT NULL, and FOREIGN KEY constraints will be removed from the
- * AT_AddColumn AlterTableCmd by analyze.c and added as independent
+ * AT_AddColumn AlterTableCmd by parse_utilcmd.c and added as independent
* AlterTableCmd's.
*/
static void
@@ -3745,9 +3745,9 @@ ATExecDropColumn(Relation rel, const char *colName,
/*
* ALTER TABLE ADD INDEX
*
- * There is no such command in the grammar, but the parser converts UNIQUE
- * and PRIMARY KEY constraints into AT_AddIndex subcommands. This lets us
- * schedule creation of the index at the appropriate time during ALTER.
+ * There is no such command in the grammar, but parse_utilcmd.c converts
+ * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands. This lets
+ * us schedule creation of the index at the appropriate time during ALTER.
*/
static void
ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
@@ -3766,13 +3766,8 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
/* suppress notices when rebuilding existing index */
quiet = is_rebuild;
- /*
- * Run parse analysis. We don't have convenient access to the query text
- * here, but it's probably not worth worrying about.
- */
- stmt = analyzeIndexStmt(stmt, NULL);
+ /* The IndexStmt has already been through transformIndexStmt */
- /* ... and do it */
DefineIndex(stmt->relation, /* relation */
stmt->idxname, /* index name */
InvalidOid, /* no predefined OID */
@@ -3806,7 +3801,7 @@ ATExecAddConstraint(AlteredTableInfo *tab, Relation rel, Node *newConstraint)
/*
* Currently, we only expect to see CONSTR_CHECK nodes
* arriving here (see the preprocessing done in
- * parser/analyze.c). Use a switch anyway to make it easier
+ * parse_utilcmd.c). Use a switch anyway to make it easier
* to add more code later.
*/
switch (constr->contype)
@@ -5239,17 +5234,27 @@ ATPostAlterTypeParse(char *cmd, List **wqueue)
ListCell *list_item;
/*
- * We expect that we only have to do raw parsing and parse analysis, not
- * any rule rewriting, since these will all be utility statements.
+ * We expect that we will get only ALTER TABLE and CREATE INDEX statements.
+ * Hence, there is no need to pass them through parse_analyze() or the
+ * rewriter, but instead we need to pass them through parse_utilcmd.c
+ * to make them ready for execution.
*/
raw_parsetree_list = raw_parser(cmd);
querytree_list = NIL;
foreach(list_item, raw_parsetree_list)
{
- Node *parsetree = (Node *) lfirst(list_item);
-
- querytree_list = list_concat(querytree_list,
- parse_analyze(parsetree, cmd, NULL, 0));
+ Node *stmt = (Node *) lfirst(list_item);
+
+ if (IsA(stmt, IndexStmt))
+ querytree_list = lappend(querytree_list,
+ transformIndexStmt((IndexStmt *) stmt,
+ cmd));
+ else if (IsA(stmt, AlterTableStmt))
+ querytree_list = list_concat(querytree_list,
+ transformAlterTableStmt((AlterTableStmt *) stmt,
+ cmd));
+ else
+ querytree_list = lappend(querytree_list, stmt);
}
/*
@@ -5258,17 +5263,15 @@ ATPostAlterTypeParse(char *cmd, List **wqueue)
*/
foreach(list_item, querytree_list)
{
- Query *query = (Query *) lfirst(list_item);
+ Node *stm = (Node *) lfirst(list_item);
Relation rel;
AlteredTableInfo *tab;
- Assert(IsA(query, Query));
- Assert(query->commandType == CMD_UTILITY);
- switch (nodeTag(query->utilityStmt))
+ switch (nodeTag(stm))
{
case T_IndexStmt:
{
- IndexStmt *stmt = (IndexStmt *) query->utilityStmt;
+ IndexStmt *stmt = (IndexStmt *) stm;
AlterTableCmd *newcmd;
rel = relation_openrv(stmt->relation, AccessExclusiveLock);
@@ -5283,7 +5286,7 @@ ATPostAlterTypeParse(char *cmd, List **wqueue)
}
case T_AlterTableStmt:
{
- AlterTableStmt *stmt = (AlterTableStmt *) query->utilityStmt;
+ AlterTableStmt *stmt = (AlterTableStmt *) stm;
ListCell *lcmd;
rel = relation_openrv(stmt->relation, AccessExclusiveLock);
@@ -5313,7 +5316,7 @@ ATPostAlterTypeParse(char *cmd, List **wqueue)
}
default:
elog(ERROR, "unexpected statement type: %d",
- (int) nodeTag(query->utilityStmt));
+ (int) nodeTag(stm));
}
}
}
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 83f26f73ffb..f8dac126439 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.100 2007/03/13 00:33:40 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.101 2007/06/23 22:12:50 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -351,7 +351,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
void
DefineView(ViewStmt *stmt, const char *queryString)
{
- List *stmts;
Query *viewParse;
Oid viewOid;
RangeVar *view;
@@ -363,15 +362,12 @@ DefineView(ViewStmt *stmt, const char *queryString)
* Since parse analysis scribbles on its input, copy the raw parse tree;
* this ensures we don't corrupt a prepared statement, for example.
*/
- stmts = parse_analyze((Node *) copyObject(stmt->query),
- queryString, NULL, 0);
+ viewParse = parse_analyze((Node *) copyObject(stmt->query),
+ queryString, NULL, 0);
/*
* The grammar should ensure that the result is a single SELECT Query.
*/
- if (list_length(stmts) != 1)
- elog(ERROR, "unexpected parse analysis result");
- viewParse = (Query *) linitial(stmts);
if (!IsA(viewParse, Query) ||
viewParse->commandType != CMD_SELECT)
elog(ERROR, "unexpected parse analysis result");
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 9b7f42e4631..d9d61d4f0bb 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/makefuncs.c,v 1.55 2007/03/17 00:11:03 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/makefuncs.c,v 1.56 2007/06/23 22:12:50 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -135,6 +135,20 @@ flatCopyTargetEntry(TargetEntry *src_tle)
}
/*
+ * makeFromExpr -
+ * creates a FromExpr node
+ */
+FromExpr *
+makeFromExpr(List *fromlist, Node *quals)
+{
+ FromExpr *f = makeNode(FromExpr);
+
+ f->fromlist = fromlist;
+ f->quals = quals;
+ return f;
+}
+
+/*
* makeConst -
* creates a Const node
*/
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 2cf0ffd28b0..41215446a35 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.246 2007/06/11 01:16:23 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.247 2007/06/23 22:12:50 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -2910,7 +2910,6 @@ inline_function(Oid funcid, Oid result_type, List *args,
MemoryContext mycxt;
ErrorContextCallback sqlerrcontext;
List *raw_parsetree_list;
- List *querytree_list;
Query *querytree;
Node *newexpr;
int *usecounts;
@@ -2986,13 +2985,8 @@ inline_function(Oid funcid, Oid result_type, List *args,
if (list_length(raw_parsetree_list) != 1)
goto fail;
- querytree_list = parse_analyze(linitial(raw_parsetree_list), src,
- argtypes, funcform->pronargs);
-
- if (list_length(querytree_list) != 1)
- goto fail;
-
- querytree = (Query *) linitial(querytree_list);
+ querytree = parse_analyze(linitial(raw_parsetree_list), src,
+ argtypes, funcform->pronargs);
/*
* The single command must be a simple "SELECT expression".
@@ -3025,7 +3019,7 @@ inline_function(Oid funcid, Oid result_type, List *args,
* no rewriting was needed; that's probably not important, but let's be
* careful.
*/
- if (check_sql_fn_retval(funcid, result_type, querytree_list, NULL))
+ if (check_sql_fn_retval(funcid, result_type, list_make1(querytree), NULL))
goto fail; /* reject whole-tuple-result cases */
/*
diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile
index 3099f77ca64..2296dcb6206 100644
--- a/src/backend/parser/Makefile
+++ b/src/backend/parser/Makefile
@@ -2,7 +2,7 @@
#
# Makefile for parser
#
-# $PostgreSQL: pgsql/src/backend/parser/Makefile,v 1.44 2006/05/27 17:38:45 tgl Exp $
+# $PostgreSQL: pgsql/src/backend/parser/Makefile,v 1.45 2007/06/23 22:12:51 tgl Exp $
#
#-------------------------------------------------------------------------
@@ -14,7 +14,7 @@ override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
OBJS= analyze.o gram.o keywords.o parser.o parse_agg.o parse_clause.o \
parse_expr.o parse_func.o parse_node.o parse_oper.o parse_relation.o \
- parse_type.o parse_coerce.o parse_target.o scansup.o
+ parse_type.o parse_coerce.o parse_target.o parse_utilcmd.o scansup.o
FLEXFLAGS = -CF
diff --git a/src/backend/parser/README b/src/backend/parser/README
index effa9008fa9..35a7a687636 100644
--- a/src/backend/parser/README
+++ b/src/backend/parser/README
@@ -1,20 +1,21 @@
This directory does more than tokenize and parse SQL queries. It also
-creates Query structures for the various complex queries that is passed
+creates Query structures for the various complex queries that are passed
to the optimizer and then executor.
parser.c things start here
scan.l break query into tokens
-scansup.c handle escapes in input
+scansup.c handle escapes in input strings
keywords.c turn keywords into specific tokens
gram.y parse the tokens and fill query-type-specific structures
-analyze.c handle post-parse processing for each query type
+analyze.c top level of parse analysis for optimizable queries
parse_clause.c handle clauses like WHERE, ORDER BY, GROUP BY, ...
-parse_coerce.c used for coercing expressions of different types
+parse_coerce.c handle coercing expressions to different types
parse_expr.c handle expressions like col, col + 3, x = 3 or x = 4
-parse_oper.c handle operations in expressions
+parse_oper.c handle operators in expressions
parse_agg.c handle aggregates, like SUM(col1), AVG(col2), ...
parse_func.c handle functions, table.column and column identifiers
parse_node.c create nodes for various structures
parse_target.c handle the result list of the query
parse_relation.c support routines for tables and column handling
parse_type.c support routines for type handling
+parse_utilcmd.c parse analysis for utility commands (done at execution time)
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index a676bf11025..3135d852467 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -10,92 +10,34 @@
* 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.
- * parse_analyze does do some purely syntactic transformations on CREATE TABLE
- * and ALTER TABLE, but that's about it. In cases where this module contains
- * mechanisms that are useful for utility statements, we provide separate
- * subroutines that should be called at the beginning of utility execution;
- * an example is analyzeIndexStmt.
+ * DECLARE CURSOR and EXPLAIN are exceptions because they contain
+ * optimizable statements.
*
*
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.366 2007/06/20 18:21:00 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.367 2007/06/23 22:12:51 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
-#include "access/heapam.h"
-#include "catalog/heap.h"
-#include "catalog/index.h"
-#include "catalog/namespace.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 "optimizer/var.h"
#include "parser/analyze.h"
-#include "parser/gramparse.h"
#include "parser/parse_agg.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
-#include "parser/parse_type.h"
#include "parser/parsetree.h"
-#include "rewrite/rewriteManip.h"
-#include "utils/acl.h"
-#include "utils/builtins.h"
-#include "utils/lsyscache.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;
@@ -103,50 +45,24 @@ typedef struct
} check_parameter_resolution_context;
-static List *do_parse_analyze(Node *parseTree, ParseState *pstate);
-static Query *transformStmt(ParseState *pstate, Node *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 *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
static List *transformInsertRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos);
-static List *transformReturningList(ParseState *pstate, List *returningList);
static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt);
+static void getSetColTypes(ParseState *pstate, Node *node,
+ List **colTypes, List **colTypmods);
+static void applyColumnNames(List *dst, List *src);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
+static List *transformReturningList(ParseState *pstate, List *returningList);
static Query *transformDeclareCursorStmt(ParseState *pstate,
DeclareCursorStmt *stmt);
static Query *transformExplainStmt(ParseState *pstate,
ExplainStmt *stmt);
-static 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 void getSetColTypes(ParseState *pstate, Node *node,
- List **colTypes, List **colTypmods);
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);
@@ -161,28 +77,27 @@ static bool check_parameter_resolution_walker(Node *node,
* 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 most 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;
pstate->p_sourcetext = sourceText;
pstate->p_paramtypes = paramTypes;
pstate->p_numparams = numParams;
pstate->p_variableparams = false;
- result = do_parse_analyze(parseTree, pstate);
+ query = transformStmt(pstate, parseTree);
- pfree(pstate);
+ free_parsestate(pstate);
- return result;
+ return query;
}
/*
@@ -192,24 +107,24 @@ parse_analyze(Node *parseTree, const char *sourceText,
* symbol datatypes from context. The passed-in paramTypes[] array can
* be modified or enlarged (via repalloc).
*/
-List *
+Query *
parse_analyze_varparams(Node *parseTree, const char *sourceText,
Oid **paramTypes, int *numParams)
{
ParseState *pstate = make_parsestate(NULL);
- List *result;
+ Query *query;
pstate->p_sourcetext = sourceText;
pstate->p_paramtypes = *paramTypes;
pstate->p_numparams = *numParams;
pstate->p_variableparams = true;
- result = do_parse_analyze(parseTree, pstate);
+ query = transformStmt(pstate, parseTree);
*paramTypes = pstate->p_paramtypes;
*numParams = pstate->p_numparams;
- pfree(pstate);
+ free_parsestate(pstate);
/* make sure all is well with parameter types */
if (*numParams > 0)
@@ -218,100 +133,37 @@ parse_analyze_varparams(Node *parseTree, const char *sourceText,
context.paramTypes = *paramTypes;
context.numParams = *numParams;
- check_parameter_resolution_walker((Node *) result, &context);
+ check_parameter_resolution_walker((Node *) query, &context);
}
- return result;
+ return query;
}
/*
* parse_sub_analyze
* Entry point for recursively analyzing a sub-statement.
*/
-List *
+Query *
parse_sub_analyze(Node *parseTree, ParseState *parentParseState)
{
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);
-
- 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))
{
@@ -319,8 +171,7 @@ transformStmt(ParseState *pstate, Node *parseTree,
* Optimizable statements
*/
case T_InsertStmt:
- result = transformInsertStmt(pstate, (InsertStmt *) parseTree,
- extras_before, extras_after);
+ result = transformInsertStmt(pstate, (InsertStmt *) parseTree);
break;
case T_DeleteStmt:
@@ -345,20 +196,6 @@ transformStmt(ParseState *pstate, Node *parseTree,
break;
/*
- * Non-optimizable statements
- */
- case T_CreateStmt:
- result = transformCreateStmt(pstate, (CreateStmt *) parseTree,
- extras_before, extras_after);
- break;
-
- case T_AlterTableStmt:
- result = transformAlterTableStmt(pstate,
- (AlterTableStmt *) parseTree,
- extras_before, extras_after);
- break;
-
- /*
* Special cases
*/
case T_DeclareCursorStmt:
@@ -387,17 +224,6 @@ transformStmt(ParseState *pstate, Node *parseTree,
result->querySource = QSRC_ORIGINAL;
result->canSetTag = true;
- /*
- * Check that we did not produce too many resnos; at the very least we
- * cannot allow more than 2^16, since that would exceed the range of a
- * AttrNumber. It seems safest to use MaxTupleAttributeNumber.
- */
- if (pstate->p_next_resno - 1 > MaxTupleAttributeNumber)
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("target lists can have at most %d entries",
- MaxTupleAttributeNumber)));
-
return result;
}
@@ -450,8 +276,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
* transform an Insert Statement
*/
static Query *
-transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
- List **extras_before, List **extras_after)
+transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
{
Query *qry = makeNode(Query);
SelectStmt *selectStmt = (SelectStmt *) stmt->selectStmt;
@@ -552,15 +377,9 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
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);
/* The grammar should have produced a SELECT, but it might have INTO */
Assert(IsA(selectQuery, Query));
@@ -849,1210 +668,13 @@ transformInsertRow(ParseState *pstate, List *exprlist,
return result;
}
-/*
- * 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
- */
-static Query *
-transformCreateStmt(ParseState *pstate, CreateStmt *stmt,
- List **extras_before, List **extras_after)
-{
- 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);
-
- /*
- * Run through each primary element in the table creation clause. Separate
- * column defs from constraints, and do preliminary analysis.
- */
- foreach(elements, stmt->tableElts)
- {
- Node *element = lfirst(elements);
-
- switch (nodeTag(element))
- {
- case T_ColumnDef:
- transformColumnDefinition(pstate, &cxt,
- (ColumnDef *) element);
- break;
-
- case T_Constraint:
- transformTableConstraint(pstate, &cxt,
- (Constraint *) element);
- break;
-
- case T_FkConstraint:
- /* No pre-transformation needed */
- cxt.fkconstraints = lappend(cxt.fkconstraints, element);
- break;
-
- case T_InhRelation:
- transformInhRelation(pstate, &cxt,
- (InhRelation *) element);
- break;
-
- default:
- elog(ERROR, "unrecognized node type: %d",
- (int) nodeTag(element));
- break;
- }
- }
-
- /*
- * transformIndexConstraints wants cxt.alist to contain only index
- * statements, so transfer anything we already have into extras_after
- * immediately.
- */
- *extras_after = list_concat(cxt.alist, *extras_after);
- cxt.alist = NIL;
-
- 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;
-}
-
-static void
-transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt,
- ColumnDef *column)
-{
- bool is_serial;
- bool saw_nullable;
- bool saw_default;
- 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;
- AlterSeqStmt *altseqstmt;
- List *attnamelist;
-
- /*
- * 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);
-
- /*
- * Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence
- * as owned by this column, and add it to the list of things to be
- * done after this CREATE/ALTER TABLE.
- */
- altseqstmt = makeNode(AlterSeqStmt);
- altseqstmt->sequence = makeRangeVar(snamespace, sname);
- attnamelist = list_make3(makeString(snamespace),
- makeString(cxt->relation->relname),
- makeString(column->colname));
- altseqstmt->options = list_make1(makeDefElem("owned_by",
- (Node *) attnamelist));
-
- cxt->alist = lappend(cxt->alist, altseqstmt);
-
- /*
- * 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;
- saw_default = false;
-
- foreach(clist, column->constraints)
- {
- 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));
-
- switch (constraint->contype)
- {
- case CONSTR_NULL:
- if (saw_nullable && column->is_not_null)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
- column->colname, cxt->relation->relname)));
- column->is_not_null = FALSE;
- saw_nullable = true;
- break;
-
- case CONSTR_NOTNULL:
- if (saw_nullable && !column->is_not_null)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
- column->colname, cxt->relation->relname)));
- column->is_not_null = TRUE;
- saw_nullable = true;
- break;
-
- case CONSTR_DEFAULT:
- if (saw_default)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("multiple default values specified for column \"%s\" of table \"%s\"",
- column->colname, cxt->relation->relname)));
- /* Note: DEFAULT NULL maps to constraint->raw_expr == NULL */
- column->raw_default = constraint->raw_expr;
- Assert(constraint->cooked_expr == NULL);
- saw_default = true;
- 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;
- }
- }
-}
-
-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
- *
- * Change the LIKE <subtable> portion of a CREATE TABLE statement into
- * column definitions which recreate the user defined column portions of
- * <subtable>.
- *
- * Note: because we do this at parse analysis time, any change in the
- * referenced table between parse analysis and execution won't be reflected
- * into the new table. Is this OK?
- */
-static void
-transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
- InhRelation *inhRelation)
-{
- 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;
-
- 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);
- }
- }
-
- if (including_indexes)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("LIKE INCLUDING INDEXES is not implemented")));
-
- /*
- * Insert the copied attributes into the cxt for the new table
- * definition.
- */
- for (parent_attno = 1; parent_attno <= tupleDesc->natts;
- parent_attno++)
- {
- Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
- char *attributeName = NameStr(attribute->attname);
- ColumnDef *def;
-
- /*
- * Ignore dropped columns in the parent.
- */
- if (attribute->attisdropped)
- continue;
-
- /*
- * Create a new column, which is marked as NOT inherited.
- *
- * 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;
-
- /*
- * Add to column list
- */
- cxt->columns = lappend(cxt->columns, def);
-
- /*
- * Copy default, if present 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);
-
- /*
- * 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.
- */
-
- def->cooked_default = pstrdup(this_default);
- }
- }
-
- /*
- * Copy CHECK constraints if requested, being careful to adjust
- * attribute numbers
- */
- if (including_constraints && tupleDesc->constr)
- {
- AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
- int ccnum;
-
- 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 = NULL;
- n->cooked_expr = nodeToString(ccbin_node);
- n->indexspace = NULL;
- cxt->ckconstraints = lappend(cxt->ckconstraints, (Node *) n);
- }
- }
-
- /*
- * 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.
- */
- heap_close(relation, NoLock);
-}
-
-static void
-transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
-{
- IndexStmt *index;
- List *indexlist = NIL;
- ListCell *listptr;
- ListCell *l;
-
- /*
- * 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.
- */
- 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;
- index->concurrent = false;
-
- /*
- * 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;
- iparam->ordering = SORTBY_DEFAULT;
- iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
- index->indexParams = lappend(index->indexParams, iparam);
- }
-
- indexlist = lappend(indexlist, index);
- }
-
- /*
- * 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. However, that seems to risk race
- * conditions since we can't be sure the command will be executed
- * immediately.
- */
- Assert(cxt->alist == NIL);
- if (cxt->pkey != NULL)
- {
- /* Make sure we keep the PKEY index in preference to others... */
- cxt->alist = list_make1(cxt->pkey);
- }
-
- foreach(l, indexlist)
- {
- 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);
- }
-}
-
-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)
- {
- 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);
- }
-}
-
-/*
- * analyzeIndexStmt - perform parse analysis for CREATE INDEX
- *
- * Note that this has to be performed during execution not parse analysis, so
- * it's called by ProcessUtility. (Most other callers don't need to bother,
- * because this is a no-op for an index not using either index expressions or
- * a predicate expression.)
- */
-IndexStmt *
-analyzeIndexStmt(IndexStmt *stmt, const char *queryString)
-{
- Relation rel;
- ParseState *pstate;
- RangeTblEntry *rte;
- ListCell *l;
-
- /*
- * We must not scribble on the passed-in IndexStmt, so copy it. (This
- * is overkill, but easy.)
- */
- stmt = (IndexStmt *) copyObject(stmt);
-
- /*
- * Open the parent table with appropriate locking. We must do this
- * because addRangeTableEntry() would acquire only AccessShareLock,
- * leaving DefineIndex() needing to do a lock upgrade with consequent
- * risk of deadlock. Make sure this stays in sync with the type of
- * lock DefineIndex() wants.
- */
- rel = heap_openrv(stmt->relation,
- (stmt->concurrent ? ShareUpdateExclusiveLock : ShareLock));
-
- /* Set up pstate */
- pstate = make_parsestate(NULL);
- pstate->p_sourcetext = queryString;
-
- /*
- * Put the parent table into the rtable so that the expressions can
- * refer to its fields without qualification.
- */
- rte = addRangeTableEntry(pstate, stmt->relation, NULL, false, true);
-
- /* no to join list, yes to namespaces */
- addRTEtoQuery(pstate, rte, false, true, true);
-
- /* take care of the where clause */
- if (stmt->whereClause)
- stmt->whereClause = transformWhereClause(pstate,
- stmt->whereClause,
- "WHERE");
-
- /* take care of any index expressions */
- foreach(l, stmt->indexParams)
- {
- IndexElem *ielem = (IndexElem *) lfirst(l);
-
- if (ielem->expr)
- {
- 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 cannot return a set")));
- }
- }
-
- /*
- * Check that only the base rel is mentioned.
- */
- if (list_length(pstate->p_rtable) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
- errmsg("index expressions and predicates can refer only to the table being indexed")));
-
- release_pstate_resources(pstate);
- pfree(pstate);
-
- /* Close relation, but keep the lock */
- heap_close(rel, NoLock);
-
- return stmt;
-}
-
-
-/*
- * analyzeRuleStmt -
- * transform a Create Rule Statement. The action is a list of parse
- * trees which is transformed into a list of query trees, and we also
- * transform the WHERE clause if any.
- *
- * Note that this has to be performed during execution not parse analysis,
- * so it's called by DefineRule. Also note that we must not scribble on
- * the passed-in RuleStmt, so we do copyObject() on the actions and WHERE
- * clause.
- */
-void
-analyzeRuleStmt(RuleStmt *stmt, const char *queryString,
- List **actions, Node **whereClause)
-{
- Relation rel;
- ParseState *pstate;
- RangeTblEntry *oldrte;
- RangeTblEntry *newrte;
-
- /*
- * 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);
-
- /* Set up pstate */
- pstate = make_parsestate(NULL);
- pstate->p_sourcetext = queryString;
-
- /*
- * 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.
- */
- 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;
-
- /*
- * 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)
- {
- 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;
- }
-
- /* take care of the where clause */
- *whereClause = transformWhereClause(pstate,
- (Node *) copyObject(stmt->whereClause),
- "WHERE");
-
- if (list_length(pstate->p_rtable) != 2) /* naughty, naughty... */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("rule WHERE condition cannot 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")));
-
- /*
- * '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.
- */
- 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 */
-
- *actions = list_make1(nothing_qry);
- }
- else
- {
- 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(NULL);
- Query *sub_qry,
- *top_subqry;
- List *extras_before = NIL;
- List *extras_after = NIL;
- bool has_old,
- has_new;
-
- /*
- * Since outer ParseState isn't parent of inner, have to pass
- * down the query text by hand.
- */
- sub_pstate->p_sourcetext = queryString;
-
- /*
- * 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,
- (Node *) copyObject(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 &&
- *whereClause != NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("rules with WHERE conditions can only have SELECT, INSERT, UPDATE, or DELETE actions")));
-
- /*
- * 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 && *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(*whereClause, PRS2_OLD_VARNO, 0);
- has_new =
- rangeTableEntry_used((Node *) sub_qry, PRS2_NEW_VARNO, 0) ||
- rangeTableEntry_used(*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 cannot use OLD")));
- if (has_new)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("ON SELECT rule cannot 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 cannot use OLD")));
- break;
- case CMD_DELETE:
- if (has_new)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("ON DELETE rule cannot 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 = list_concat(newactions, extras_before);
- newactions = lappend(newactions, top_subqry);
- newactions = list_concat(newactions, extras_after);
-
- release_pstate_resources(sub_pstate);
- pfree(sub_pstate);
- }
-
- *actions = newactions;
- }
-
- release_pstate_resources(pstate);
- pfree(pstate);
-
- /* Close relation, but keep the exclusive lock */
- heap_close(rel, NoLock);
-}
-
/*
* transformSelectStmt -
* transforms a Select Statement
*
- * Note: this is also used for DECLARE CURSOR statements.
+ * Note: this covers only cases with no set operations and no VALUES lists;
+ * see below for the other cases.
*/
static Query *
transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
@@ -2323,7 +945,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
}
/*
- * transformSetOperationsStmt -
+ * transformSetOperationStmt -
* transforms a set-operations tree
*
* A set-operation tree is just a SELECT, but with UNION/INTERSECT/EXCEPT
@@ -2591,7 +1213,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
if (isLeaf)
{
/* Process leaf SELECT */
- List *selectList;
Query *selectQuery;
char selectName[32];
RangeTblEntry *rte;
@@ -2604,11 +1225,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
* of this sub-query, because they are not in the toplevel pstate's
* namespace list.
*/
- selectList = parse_sub_analyze((Node *) stmt, pstate);
-
- Assert(list_length(selectList) == 1);
- selectQuery = (Query *) linitial(selectList);
- Assert(IsA(selectQuery, Query));
+ selectQuery = parse_sub_analyze((Node *) stmt, pstate);
/*
* Check for bogus references to Vars on the current query level (but
@@ -2761,7 +1378,10 @@ getSetColTypes(ParseState *pstate, Node *node,
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
}
-/* 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)
{
@@ -2957,185 +1577,6 @@ transformReturningList(ParseState *pstate, List *returningList)
return rlist;
}
-/*
- * transformAlterTableStmt -
- * transform an Alter Table Statement
- *
- * CAUTION: resist the temptation to do any work here that depends on the
- * current state of the table. Actual execution of the command might not
- * occur till some future transaction. Hence, we do only purely syntactic
- * transformations here, comparable to the processing of CREATE TABLE.
- */
-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:
-
- /*
- * Already-transformed ADD CONSTRAINT, so just make it look
- * like the standard case.
- */
- cmd->subtype = AT_AddConstraint;
- newcmds = lappend(newcmds, cmd);
- break;
-
- default:
- newcmds = lappend(newcmds, cmd);
- break;
- }
- }
-
- /*
- * transformIndexConstraints wants cxt.alist to contain only index
- * statements, so transfer anything we already have into extras_after
- * immediately.
- */
- *extras_after = list_concat(cxt.alist, *extras_after);
- cxt.alist = NIL;
-
- /* Postprocess index and FK constraints */
- transformIndexConstraints(pstate, &cxt);
-
- transformFKConstraints(pstate, &cxt, skipValidation, true);
-
- /*
- * Push any index-creation commands into the ALTER, so that they can be
- * scheduled nicely by tablecmds.c.
- */
- foreach(l, cxt.alist)
- {
- Node *idxstmt = (Node *) lfirst(l);
-
- Assert(IsA(idxstmt, IndexStmt));
- newcmd = makeNode(AlterTableCmd);
- newcmd->subtype = AT_AddIndex;
- newcmd->def = idxstmt;
- newcmds = lappend(newcmds, newcmd);
- }
- cxt.alist = NIL;
-
- /* 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)
- {
- newcmd = makeNode(AlterTableCmd);
- newcmd->subtype = AT_AddConstraint;
- newcmd->def = (Node *) lfirst(l);
- newcmds = lappend(newcmds, newcmd);
- }
-
- /* Update statement's commands list */
- stmt->cmds = newcmds;
-
- qry = makeNode(Query);
- qry->commandType = CMD_UTILITY;
- qry->utilityStmt = (Node *) stmt;
-
- *extras_before = list_concat(*extras_before, cxt.blist);
- *extras_after = list_concat(cxt.alist, *extras_after);
-
- return qry;
-}
-
/*
* transformDeclareCursorStmt -
@@ -3152,8 +1593,6 @@ static Query *
transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
{
Query *result;
- List *extras_before = NIL,
- *extras_after = NIL;
/*
* Don't allow both SCROLL and NO SCROLL to be specified
@@ -3164,12 +1603,8 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("cannot specify both SCROLL and NO SCROLL")));
- result = transformStmt(pstate, stmt->query,
- &extras_before, &extras_after);
+ result = transformStmt(pstate, stmt->query);
- /* Shouldn't get any extras, since grammar only allows SelectStmt */
- if (extras_before || extras_after)
- elog(ERROR, "unexpected extra stuff in cursor statement");
if (!IsA(result, Query) ||
result->commandType != CMD_SELECT ||
result->utilityStmt != NULL)
@@ -3219,12 +1654,8 @@ transformExplainStmt(ParseState *pstate, ExplainStmt *stmt)
if (pstate->p_variableparams)
{
- List *extras_before = NIL,
- *extras_after = NIL;
-
/* Since parse analysis scribbles on its input, copy the tree first! */
- (void) transformStmt(pstate, copyObject(stmt->query),
- &extras_before, &extras_after);
+ (void) transformStmt(pstate, copyObject(stmt->query));
}
/* Now return the untransformed command as a utility Query */
@@ -3420,281 +1851,6 @@ applyLockingClause(Query *qry, Index rtindex, bool forUpdate, bool noWait)
/*
- * 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,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6e894c1799a..d9e981bb572 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.595 2007/06/18 21:40:57 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.596 2007/06/23 22:12:51 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -33,7 +33,7 @@
* SET SQL_inheritance TO off; SELECT * FROM foo;
* because the entire string is parsed by gram.y before the SET gets
* executed. Anything that depends on the database or changeable state
- * should be handled inside parse_analyze() so that it happens at the
+ * should be handled during parse analysis so that it happens at the
* right time not the wrong time. The handling of SQL_inheritance is
* a good example.
*
@@ -2093,9 +2093,10 @@ ColConstraintElem:
* ConstraintAttr represents constraint attributes, which we parse as if
* they were independent constraint clauses, in order to avoid shift/reduce
* conflicts (since NOT might start either an independent NOT NULL clause
- * or an attribute). analyze.c is responsible for attaching the attribute
- * information to the preceding "real" constraint node, and for complaining
- * if attribute clauses appear in the wrong place or wrong combinations.
+ * or an attribute). parse_utilcmd.c is responsible for attaching the
+ * attribute information to the preceding "real" constraint node, and for
+ * complaining if attribute clauses appear in the wrong place or wrong
+ * combinations.
*
* See also ConstraintAttributeSpec, which can be used in places where
* there is no parsing conflict.
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 4c1fb0cc4c1..28717020e34 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.165 2007/04/27 22:05:48 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.166 2007/06/23 22:12:51 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -152,8 +152,8 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
* Open target rel and grab suitable lock (which we will hold till end of
* transaction).
*
- * analyze.c will eventually do the corresponding heap_close(), but *not*
- * release the lock.
+ * free_parsestate() will eventually do the corresponding
+ * heap_close(), but *not* release the lock.
*/
pstate->p_target_relation = heap_openrv(relation, RowExclusiveLock);
@@ -193,7 +193,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
* Simplify InhOption (yes/no/default) into boolean yes/no.
*
* The reason we do things this way is that we don't want to examine the
- * SQL_inheritance option flag until parse_analyze is run. Otherwise,
+ * SQL_inheritance option flag until parse_analyze() is run. Otherwise,
* we'd do the wrong thing with query strings that intermix SET commands
* with queries.
*/
@@ -417,7 +417,6 @@ transformTableEntry(ParseState *pstate, RangeVar *r)
static RangeTblEntry *
transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
{
- List *parsetrees;
Query *query;
RangeTblEntry *rte;
@@ -434,19 +433,12 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
/*
* Analyze and transform the subquery.
*/
- parsetrees = parse_sub_analyze(r->subquery, pstate);
+ query = parse_sub_analyze(r->subquery, pstate);
/*
- * Check that we got something reasonable. Most of these conditions are
- * probably impossible given restrictions of the grammar, but check 'em
- * anyway.
+ * Check that we got something reasonable. Many of these conditions are
+ * impossible given restrictions of the grammar, but check 'em anyway.
*/
- if (list_length(parsetrees) != 1)
- elog(ERROR, "unexpected parse analysis result for subquery in FROM");
- query = (Query *) linitial(parsetrees);
- if (query == NULL || !IsA(query, Query))
- elog(ERROR, "unexpected parse analysis result for subquery in FROM");
-
if (query->commandType != CMD_SELECT ||
query->utilityStmt != NULL)
elog(ERROR, "expected SELECT query from subquery in FROM");
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index da4bcf208f9..754e18d687c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.220 2007/06/11 22:22:42 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.221 2007/06/23 22:12:51 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1120,19 +1120,15 @@ transformCaseExpr(ParseState *pstate, CaseExpr *c)
static Node *
transformSubLink(ParseState *pstate, SubLink *sublink)
{
- List *qtrees;
- Query *qtree;
Node *result = (Node *) sublink;
+ Query *qtree;
/* If we already transformed this node, do nothing */
if (IsA(sublink->subselect, Query))
return result;
pstate->p_hasSubLinks = true;
- qtrees = parse_sub_analyze(sublink->subselect, pstate);
- if (list_length(qtrees) != 1)
- elog(ERROR, "bad query in sub-select");
- qtree = (Query *) linitial(qtrees);
+ qtree = parse_sub_analyze(sublink->subselect, pstate);
if (qtree->commandType != CMD_SELECT ||
qtree->utilityStmt != NULL ||
qtree->intoClause != NULL)
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index a8dfa2666bb..a5e6026ebab 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -8,12 +8,13 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_node.c,v 1.97 2007/03/17 00:11:04 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_node.c,v 1.98 2007/06/23 22:12:51 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
+#include "access/heapam.h"
#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "nodes/makefuncs.h"
@@ -27,9 +28,11 @@
#include "utils/varbit.h"
-/* make_parsestate()
- * Allocate and initialize a new ParseState.
- * The CALLER is responsible for freeing the ParseState* returned.
+/*
+ * make_parsestate
+ * Allocate and initialize a new ParseState.
+ *
+ * Caller should eventually release the ParseState via free_parsestate().
*/
ParseState *
make_parsestate(ParseState *parentParseState)
@@ -52,6 +55,30 @@ make_parsestate(ParseState *parentParseState)
return pstate;
}
+/*
+ * free_parsestate
+ * Release a ParseState and any subsidiary resources.
+ */
+void
+free_parsestate(ParseState *pstate)
+{
+ /*
+ * 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)));
+
+ if (pstate->p_target_relation != NULL)
+ heap_close(pstate->p_target_relation, NoLock);
+
+ pfree(pstate);
+}
+
/*
* parser_errposition
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
new file mode 100644
index 00000000000..37822251943
--- /dev/null
+++ b/src/backend/parser/parse_utilcmd.c
@@ -0,0 +1,1800 @@
+/*-------------------------------------------------------------------------
+ *
+ * parse_utilcmd.c
+ * Perform parse analysis work for various utility commands
+ *
+ * Formerly we did this work during parse_analyze() in analyze.c. However
+ * that is fairly unsafe in the presence of querytree caching, since any
+ * database state that we depend on in making the transformations might be
+ * obsolete by the time the utility command is executed; and utility commands
+ * have no infrastructure for holding locks or rechecking plan validity.
+ * Hence these functions are now called at the start of execution of their
+ * respective utility commands.
+ *
+ * NOTE: in general we must avoid scribbling on the passed-in raw parse
+ * tree, since it might be in a plan cache. The simplest solution is
+ * a quick copyObject() call before manipulating the query tree.
+ *
+ *
+ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.1 2007/06/23 22:12:51 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "catalog/heap.h"
+#include "catalog/index.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "commands/tablecmds.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "optimizer/clauses.h"
+#include "parser/analyze.h"
+#include "parser/gramparse.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_relation.h"
+#include "parser/parse_type.h"
+#include "parser/parse_utilcmd.h"
+#include "rewrite/rewriteManip.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+
+/* State shared by transformCreateStmt and its subroutines */
+typedef struct
+{
+ const char *stmtType; /* "CREATE TABLE" or "ALTER TABLE" */
+ RangeVar *relation; /* relation to create */
+ Relation rel; /* opened/locked rel, if ALTER */
+ List *inhRelations; /* relations to inherit from */
+ bool isalter; /* true if altering existing table */
+ bool hasoids; /* does relation have an OID column? */
+ 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;
+
+/* 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 */
+} CreateSchemaStmtContext;
+
+
+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 transformConstraintAttrs(List *constraintList);
+static void transformColumnType(ParseState *pstate, ColumnDef *column);
+static void setSchemaName(char *context_schema, char **stmt_schema_name);
+
+
+/*
+ * transformCreateStmt -
+ * parse analysis for CREATE TABLE
+ *
+ * Returns a List of utility commands to be done in sequence. One of these
+ * will be the transformed CreateStmt, but there may be additional actions
+ * to be done before and after the actual DefineRelation() call.
+ *
+ * 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
+ */
+List *
+transformCreateStmt(CreateStmt *stmt, const char *queryString)
+{
+ ParseState *pstate;
+ CreateStmtContext cxt;
+ List *result;
+ List *save_alist;
+ ListCell *elements;
+
+ /*
+ * We must not scribble on the passed-in CreateStmt, so copy it. (This
+ * is overkill, but easy.)
+ */
+ stmt = (CreateStmt *) copyObject(stmt);
+
+ /* Set up pstate */
+ pstate = make_parsestate(NULL);
+ pstate->p_sourcetext = queryString;
+
+ cxt.stmtType = "CREATE TABLE";
+ cxt.relation = stmt->relation;
+ cxt.rel = NULL;
+ 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);
+
+ /*
+ * Run through each primary element in the table creation clause. Separate
+ * column defs from constraints, and do preliminary analysis.
+ */
+ foreach(elements, stmt->tableElts)
+ {
+ Node *element = lfirst(elements);
+
+ switch (nodeTag(element))
+ {
+ case T_ColumnDef:
+ transformColumnDefinition(pstate, &cxt,
+ (ColumnDef *) element);
+ break;
+
+ case T_Constraint:
+ transformTableConstraint(pstate, &cxt,
+ (Constraint *) element);
+ break;
+
+ case T_FkConstraint:
+ /* No pre-transformation needed */
+ cxt.fkconstraints = lappend(cxt.fkconstraints, element);
+ break;
+
+ case T_InhRelation:
+ transformInhRelation(pstate, &cxt,
+ (InhRelation *) element);
+ break;
+
+ default:
+ elog(ERROR, "unrecognized node type: %d",
+ (int) nodeTag(element));
+ break;
+ }
+ }
+
+ /*
+ * transformIndexConstraints wants cxt.alist to contain only index
+ * statements, so transfer anything we already have into save_alist.
+ */
+ save_alist = cxt.alist;
+ cxt.alist = NIL;
+
+ 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.
+ */
+ stmt->tableElts = cxt.columns;
+ stmt->constraints = cxt.ckconstraints;
+
+ result = lappend(cxt.blist, stmt);
+ result = list_concat(result, cxt.alist);
+ result = list_concat(result, save_alist);
+
+ return result;
+}
+
+/*
+ * transformColumnDefinition -
+ * transform a single ColumnDef within CREATE TABLE
+ * Also used in ALTER TABLE ADD COLUMN
+ */
+static void
+transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt,
+ ColumnDef *column)
+{
+ bool is_serial;
+ bool saw_nullable;
+ bool saw_default;
+ 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;
+ AlterSeqStmt *altseqstmt;
+ List *attnamelist;
+
+ /*
+ * 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.
+ */
+ if (cxt->rel)
+ snamespaceid = RelationGetNamespace(cxt->rel);
+ else
+ 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);
+
+ /*
+ * Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence
+ * as owned by this column, and add it to the list of things to be
+ * done after this CREATE/ALTER TABLE.
+ */
+ altseqstmt = makeNode(AlterSeqStmt);
+ altseqstmt->sequence = makeRangeVar(snamespace, sname);
+ attnamelist = list_make3(makeString(snamespace),
+ makeString(cxt->relation->relname),
+ makeString(column->colname));
+ altseqstmt->options = list_make1(makeDefElem("owned_by",
+ (Node *) attnamelist));
+
+ cxt->alist = lappend(cxt->alist, altseqstmt);
+
+ /*
+ * 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;
+ saw_default = false;
+
+ foreach(clist, column->constraints)
+ {
+ 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));
+
+ switch (constraint->contype)
+ {
+ case CONSTR_NULL:
+ if (saw_nullable && column->is_not_null)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
+ column->colname, cxt->relation->relname)));
+ column->is_not_null = FALSE;
+ saw_nullable = true;
+ break;
+
+ case CONSTR_NOTNULL:
+ if (saw_nullable && !column->is_not_null)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
+ column->colname, cxt->relation->relname)));
+ column->is_not_null = TRUE;
+ saw_nullable = true;
+ break;
+
+ case CONSTR_DEFAULT:
+ if (saw_default)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("multiple default values specified for column \"%s\" of table \"%s\"",
+ column->colname, cxt->relation->relname)));
+ /* Note: DEFAULT NULL maps to constraint->raw_expr == NULL */
+ column->raw_default = constraint->raw_expr;
+ Assert(constraint->cooked_expr == NULL);
+ saw_default = true;
+ 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;
+ }
+ }
+}
+
+/*
+ * transformTableConstraint
+ * transform a Constraint node within CREATE TABLE or ALTER TABLE
+ */
+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
+ *
+ * Change the LIKE <subtable> portion of a CREATE TABLE statement into
+ * column definitions which recreate the user defined column portions of
+ * <subtable>.
+ */
+static void
+transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
+ InhRelation *inhRelation)
+{
+ 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;
+
+ 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);
+ }
+ }
+
+ if (including_indexes)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("LIKE INCLUDING INDEXES is not implemented")));
+
+ /*
+ * Insert the copied attributes into the cxt for the new table
+ * definition.
+ */
+ for (parent_attno = 1; parent_attno <= tupleDesc->natts;
+ parent_attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+ ColumnDef *def;
+
+ /*
+ * Ignore dropped columns in the parent.
+ */
+ if (attribute->attisdropped)
+ continue;
+
+ /*
+ * Create a new column, which is marked as NOT inherited.
+ *
+ * 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;
+
+ /*
+ * Add to column list
+ */
+ cxt->columns = lappend(cxt->columns, def);
+
+ /*
+ * Copy default, if present 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);
+
+ /*
+ * 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.
+ */
+
+ def->cooked_default = pstrdup(this_default);
+ }
+ }
+
+ /*
+ * Copy CHECK constraints if requested, being careful to adjust
+ * attribute numbers
+ */
+ if (including_constraints && tupleDesc->constr)
+ {
+ AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
+ int ccnum;
+
+ 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 = NULL;
+ n->cooked_expr = nodeToString(ccbin_node);
+ n->indexspace = NULL;
+ cxt->ckconstraints = lappend(cxt->ckconstraints, (Node *) n);
+ }
+ }
+
+ /*
+ * 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.
+ */
+ heap_close(relation, NoLock);
+}
+
+/*
+ * transformIndexConstraints
+ * Handle UNIQUE and PRIMARY KEY constraints, which create indexes
+ */
+static void
+transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
+{
+ IndexStmt *index;
+ List *indexlist = NIL;
+ ListCell *listptr;
+ ListCell *l;
+
+ /*
+ * 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.
+ */
+ 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;
+ index->concurrent = false;
+
+ /*
+ * 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;
+ iparam->ordering = SORTBY_DEFAULT;
+ iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+ index->indexParams = lappend(index->indexParams, iparam);
+ }
+
+ indexlist = lappend(indexlist, index);
+ }
+
+ /*
+ * 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.
+ */
+ Assert(cxt->alist == NIL);
+ if (cxt->pkey != NULL)
+ {
+ /* Make sure we keep the PKEY index in preference to others... */
+ cxt->alist = list_make1(cxt->pkey);
+ }
+
+ foreach(l, indexlist)
+ {
+ 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);
+ }
+}
+
+/*
+ * transformFKConstraints
+ * handle FOREIGN KEY constraints
+ */
+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)
+ {
+ 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);
+ }
+}
+
+/*
+ * transformIndexStmt - parse analysis for CREATE INDEX
+ *
+ * Note: this is a no-op for an index not using either index expressions or
+ * a predicate expression. There are several code paths that create indexes
+ * without bothering to call this, because they know they don't have any
+ * such expressions to deal with.
+ */
+IndexStmt *
+transformIndexStmt(IndexStmt *stmt, const char *queryString)
+{
+ Relation rel;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ /*
+ * We must not scribble on the passed-in IndexStmt, so copy it. (This
+ * is overkill, but easy.)
+ */
+ stmt = (IndexStmt *) copyObject(stmt);
+
+ /*
+ * Open the parent table with appropriate locking. We must do this
+ * because addRangeTableEntry() would acquire only AccessShareLock,
+ * leaving DefineIndex() needing to do a lock upgrade with consequent
+ * risk of deadlock. Make sure this stays in sync with the type of
+ * lock DefineIndex() wants.
+ */
+ rel = heap_openrv(stmt->relation,
+ (stmt->concurrent ? ShareUpdateExclusiveLock : ShareLock));
+
+ /* Set up pstate */
+ pstate = make_parsestate(NULL);
+ pstate->p_sourcetext = queryString;
+
+ /*
+ * Put the parent table into the rtable so that the expressions can
+ * refer to its fields without qualification.
+ */
+ rte = addRangeTableEntry(pstate, stmt->relation, NULL, false, true);
+
+ /* no to join list, yes to namespaces */
+ addRTEtoQuery(pstate, rte, false, true, true);
+
+ /* take care of the where clause */
+ if (stmt->whereClause)
+ stmt->whereClause = transformWhereClause(pstate,
+ stmt->whereClause,
+ "WHERE");
+
+ /* take care of any index expressions */
+ foreach(l, stmt->indexParams)
+ {
+ IndexElem *ielem = (IndexElem *) lfirst(l);
+
+ if (ielem->expr)
+ {
+ 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 cannot return a set")));
+ }
+ }
+
+ /*
+ * Check that only the base rel is mentioned.
+ */
+ if (list_length(pstate->p_rtable) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("index expressions and predicates can refer only to the table being indexed")));
+
+ free_parsestate(pstate);
+
+ /* Close relation, but keep the lock */
+ heap_close(rel, NoLock);
+
+ return stmt;
+}
+
+
+/*
+ * transformRuleStmt -
+ * transform a CREATE RULE Statement. The action is a list of parse
+ * trees which is transformed into a list of query trees, and we also
+ * transform the WHERE clause if any.
+ *
+ * actions and whereClause are output parameters that receive the
+ * transformed results.
+ *
+ * Note that we must not scribble on the passed-in RuleStmt, so we do
+ * copyObject() on the actions and WHERE clause.
+ */
+void
+transformRuleStmt(RuleStmt *stmt, const char *queryString,
+ List **actions, Node **whereClause)
+{
+ Relation rel;
+ ParseState *pstate;
+ RangeTblEntry *oldrte;
+ RangeTblEntry *newrte;
+
+ /*
+ * 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);
+
+ /* Set up pstate */
+ pstate = make_parsestate(NULL);
+ pstate->p_sourcetext = queryString;
+
+ /*
+ * 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.
+ */
+ 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;
+
+ /*
+ * 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)
+ {
+ 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;
+ }
+
+ /* take care of the where clause */
+ *whereClause = transformWhereClause(pstate,
+ (Node *) copyObject(stmt->whereClause),
+ "WHERE");
+
+ if (list_length(pstate->p_rtable) != 2) /* naughty, naughty... */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("rule WHERE condition cannot 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")));
+
+ /*
+ * '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.
+ */
+ 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 */
+
+ *actions = list_make1(nothing_qry);
+ }
+ else
+ {
+ 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(NULL);
+ Query *sub_qry,
+ *top_subqry;
+ bool has_old,
+ has_new;
+
+ /*
+ * Since outer ParseState isn't parent of inner, have to pass
+ * down the query text by hand.
+ */
+ sub_pstate->p_sourcetext = queryString;
+
+ /*
+ * 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,
+ (Node *) copyObject(action));
+
+ /*
+ * 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 &&
+ *whereClause != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("rules with WHERE conditions can only have SELECT, INSERT, UPDATE, or DELETE actions")));
+
+ /*
+ * 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 && *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(*whereClause, PRS2_OLD_VARNO, 0);
+ has_new =
+ rangeTableEntry_used((Node *) sub_qry, PRS2_NEW_VARNO, 0) ||
+ rangeTableEntry_used(*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 cannot use OLD")));
+ if (has_new)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("ON SELECT rule cannot 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 cannot use OLD")));
+ break;
+ case CMD_DELETE:
+ if (has_new)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("ON DELETE rule cannot 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);
+
+ free_parsestate(sub_pstate);
+ }
+
+ *actions = newactions;
+ }
+
+ free_parsestate(pstate);
+
+ /* Close relation, but keep the exclusive lock */
+ heap_close(rel, NoLock);
+}
+
+
+/*
+ * transformAlterTableStmt -
+ * parse analysis for ALTER TABLE
+ *
+ * Returns a List of utility commands to be done in sequence. One of these
+ * will be the transformed AlterTableStmt, but there may be additional actions
+ * to be done before and after the actual AlterTable() call.
+ */
+List *
+transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
+{
+ Relation rel;
+ ParseState *pstate;
+ CreateStmtContext cxt;
+ List *result;
+ List *save_alist;
+ ListCell *lcmd,
+ *l;
+ List *newcmds = NIL;
+ bool skipValidation = true;
+ AlterTableCmd *newcmd;
+
+ /*
+ * We must not scribble on the passed-in AlterTableStmt, so copy it.
+ * (This is overkill, but easy.)
+ */
+ stmt = (AlterTableStmt *) copyObject(stmt);
+
+ /*
+ * Acquire exclusive lock on the target relation, which will be held
+ * until end of transaction. This ensures any decisions we make here
+ * based on the state of the relation will still be good at execution.
+ * We must get exclusive lock now because execution will; taking a lower
+ * grade lock now and trying to upgrade later risks deadlock.
+ */
+ rel = relation_openrv(stmt->relation, AccessExclusiveLock);
+
+ /* Set up pstate */
+ pstate = make_parsestate(NULL);
+ pstate->p_sourcetext = queryString;
+
+ cxt.stmtType = "ALTER TABLE";
+ cxt.relation = stmt->relation;
+ cxt.rel = rel;
+ 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:
+
+ /*
+ * Already-transformed ADD CONSTRAINT, so just make it look
+ * like the standard case.
+ */
+ cmd->subtype = AT_AddConstraint;
+ newcmds = lappend(newcmds, cmd);
+ break;
+
+ default:
+ newcmds = lappend(newcmds, cmd);
+ break;
+ }
+ }
+
+ /*
+ * transformIndexConstraints wants cxt.alist to contain only index
+ * statements, so transfer anything we already have into save_alist.
+ * immediately.
+ */
+ save_alist = cxt.alist;
+ cxt.alist = NIL;
+
+ /* Postprocess index and FK constraints */
+ transformIndexConstraints(pstate, &cxt);
+
+ transformFKConstraints(pstate, &cxt, skipValidation, true);
+
+ /*
+ * Push any index-creation commands into the ALTER, so that they can be
+ * scheduled nicely by tablecmds.c. Note that tablecmds.c assumes that
+ * the IndexStmt attached to an AT_AddIndex subcommand has already been
+ * through transformIndexStmt.
+ */
+ foreach(l, cxt.alist)
+ {
+ Node *idxstmt = (Node *) lfirst(l);
+
+ Assert(IsA(idxstmt, IndexStmt));
+ newcmd = makeNode(AlterTableCmd);
+ newcmd->subtype = AT_AddIndex;
+ newcmd->def = (Node *) transformIndexStmt((IndexStmt *) idxstmt,
+ queryString);
+ newcmds = lappend(newcmds, newcmd);
+ }
+ cxt.alist = NIL;
+
+ /* 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)
+ {
+ newcmd = makeNode(AlterTableCmd);
+ newcmd->subtype = AT_AddConstraint;
+ newcmd->def = (Node *) lfirst(l);
+ newcmds = lappend(newcmds, newcmd);
+ }
+
+ /* Close rel but keep lock */
+ relation_close(rel, NoLock);
+
+ /*
+ * Output results.
+ */
+ stmt->cmds = newcmds;
+
+ result = lappend(cxt.blist, stmt);
+ result = list_concat(result, cxt.alist);
+ result = list_concat(result, save_alist);
+
+ return result;
+}
+
+
+/*
+ * 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;
+ }
+ }
+ }
+}
+
+/*
+ * 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);
+}
+
+
+/*
+ * transformCreateSchemaStmt -
+ * 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: this breaks the rules a little bit by modifying schema-name fields
+ * within passed-in structs. However, the transformation would be the same
+ * if done over, so it should be all right to scribble on the input to this
+ * extent.
+ */
+List *
+transformCreateSchemaStmt(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.triggers = NIL;
+ cxt.grants = 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;
+}
+
+/*
+ * setSchemaName
+ * Set or check schema name in an element of a CREATE SCHEMA command
+ */
+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)));
+}
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index e57e2ab046c..540f3403680 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.120 2007/04/27 22:05:48 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.121 2007/06/23 22:12:51 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -20,8 +20,8 @@
#include "catalog/pg_rewrite.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
-#include "parser/analyze.h"
#include "parser/parse_expr.h"
+#include "parser/parse_utilcmd.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteManip.h"
#include "rewrite/rewriteSupport.h"
@@ -191,7 +191,7 @@ DefineRule(RuleStmt *stmt, const char *queryString)
Node *whereClause;
/* Parse analysis ... */
- analyzeRuleStmt(stmt, queryString, &actions, &whereClause);
+ transformRuleStmt(stmt, queryString, &actions, &whereClause);
/* ... and execution */
DefineQueryRewrite(stmt->rulename,
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 724f2e028d0..94fd65e5d44 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.533 2007/04/30 16:37:08 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.534 2007/06/23 22:12:52 tgl Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
@@ -165,7 +165,7 @@ static int UseNewLine = 0; /* Use EOF as query delimiters */
static int InteractiveBackend(StringInfo inBuf);
static int SocketBackend(StringInfo inBuf);
static int ReadCommand(StringInfo inBuf);
-static List *pg_rewrite_queries(List *querytree_list);
+static List *pg_rewrite_query(Query *query);
static bool check_log_statement(List *stmt_list);
static int errdetail_execute(List *raw_parsetree_list);
static int errdetail_params(ParamListInfo params);
@@ -567,6 +567,7 @@ List *
pg_analyze_and_rewrite(Node *parsetree, const char *query_string,
Oid *paramTypes, int numParams)
{
+ Query *query;
List *querytree_list;
/*
@@ -575,8 +576,7 @@ pg_analyze_and_rewrite(Node *parsetree, const char *query_string,
if (log_parser_stats)
ResetUsage();
- querytree_list = parse_analyze(parsetree, query_string,
- paramTypes, numParams);
+ query = parse_analyze(parsetree, query_string, paramTypes, numParams);
if (log_parser_stats)
ShowUsage("PARSE ANALYSIS STATISTICS");
@@ -584,68 +584,55 @@ pg_analyze_and_rewrite(Node *parsetree, const char *query_string,
/*
* (2) Rewrite the queries, as necessary
*/
- querytree_list = pg_rewrite_queries(querytree_list);
+ querytree_list = pg_rewrite_query(query);
return querytree_list;
}
/*
- * Perform rewriting of a list of queries produced by parse analysis.
+ * Perform rewriting of a query produced by parse analysis.
*
- * Note: queries must just have come from the parser, because we do not do
- * AcquireRewriteLocks() on them.
+ * Note: query must just have come from the parser, because we do not do
+ * AcquireRewriteLocks() on it.
*/
static List *
-pg_rewrite_queries(List *querytree_list)
+pg_rewrite_query(Query *query)
{
- List *new_list = NIL;
- ListCell *list_item;
+ List *querytree_list;
if (log_parser_stats)
ResetUsage();
- /*
- * rewritten queries are collected in new_list. Note there may be more or
- * fewer than in the original list.
- */
- foreach(list_item, querytree_list)
- {
- Query *querytree = (Query *) lfirst(list_item);
-
- if (Debug_print_parse)
- elog_node_display(DEBUG1, "parse tree", querytree,
- Debug_pretty_print);
-
- if (querytree->commandType == CMD_UTILITY)
- {
- /* don't rewrite utilities, just dump 'em into new_list */
- new_list = lappend(new_list, querytree);
- }
- else
- {
- /* rewrite regular queries */
- List *rewritten = QueryRewrite(querytree);
+ if (Debug_print_parse)
+ elog_node_display(DEBUG1, "parse tree", query,
+ Debug_pretty_print);
- new_list = list_concat(new_list, rewritten);
- }
+ if (query->commandType == CMD_UTILITY)
+ {
+ /* don't rewrite utilities, just dump 'em into result list */
+ querytree_list = list_make1(query);
+ }
+ else
+ {
+ /* rewrite regular queries */
+ querytree_list = QueryRewrite(query);
}
-
- querytree_list = new_list;
if (log_parser_stats)
ShowUsage("REWRITER STATISTICS");
#ifdef COPY_PARSE_PLAN_TREES
+ /* Optional debugging check: pass querytree output through copyObject() */
+ {
+ List *new_list;
- /*
- * Optional debugging check: pass querytree output through copyObject()
- */
- new_list = (List *) copyObject(querytree_list);
- /* This checks both copyObject() and the equal() routines... */
- if (!equal(new_list, querytree_list))
- elog(WARNING, "copyObject() failed to produce an equal parse tree");
- else
- querytree_list = new_list;
+ new_list = (List *) copyObject(querytree_list);
+ /* This checks both copyObject() and the equal() routines... */
+ if (!equal(new_list, querytree_list))
+ elog(WARNING, "copyObject() failed to produce equal parse tree");
+ else
+ querytree_list = new_list;
+ }
#endif
if (Debug_print_rewritten)
@@ -1139,6 +1126,7 @@ exec_parse_message(const char *query_string, /* string to execute */
if (parsetree_list != NIL)
{
+ Query *query;
int i;
raw_parse_tree = (Node *) linitial(parsetree_list);
@@ -1175,10 +1163,10 @@ exec_parse_message(const char *query_string, /* string to execute */
if (log_parser_stats)
ResetUsage();
- querytree_list = parse_analyze_varparams(copyObject(raw_parse_tree),
- query_string,
- &paramTypes,
- &numParams);
+ query = parse_analyze_varparams(copyObject(raw_parse_tree),
+ query_string,
+ &paramTypes,
+ &numParams);
/*
* Check all parameter types got determined.
@@ -1197,7 +1185,7 @@ exec_parse_message(const char *query_string, /* string to execute */
if (log_parser_stats)
ShowUsage("PARSE ANALYSIS STATISTICS");
- querytree_list = pg_rewrite_queries(querytree_list);
+ querytree_list = pg_rewrite_query(query);
/*
* If this is the unnamed statement and it has parameters, defer query
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index baa203a2d19..c334743adaf 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.280 2007/05/30 20:12:01 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.281 2007/06/23 22:12:52 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -45,7 +45,7 @@
#include "commands/vacuum.h"
#include "commands/view.h"
#include "miscadmin.h"
-#include "parser/analyze.h"
+#include "parser/parse_utilcmd.h"
#include "postmaster/bgwriter.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteRemove.h"
@@ -544,17 +544,47 @@ ProcessUtility(Node *parsetree,
case T_CreateStmt:
{
+ List *stmts;
+ ListCell *l;
Oid relOid;
- relOid = DefineRelation((CreateStmt *) parsetree,
- RELKIND_RELATION);
+ /* Run parse analysis ... */
+ stmts = transformCreateStmt((CreateStmt *) parsetree,
+ queryString);
- /*
- * Let AlterTableCreateToastTable decide if this one needs a
- * secondary relation too.
- */
- CommandCounterIncrement();
- AlterTableCreateToastTable(relOid);
+ /* ... and do it */
+ foreach(l, stmts)
+ {
+ Node *stmt = (Node *) lfirst(l);
+
+ if (IsA(stmt, CreateStmt))
+ {
+ /* Create the table itself */
+ relOid = DefineRelation((CreateStmt *) stmt,
+ RELKIND_RELATION);
+
+ /*
+ * Let AlterTableCreateToastTable decide if this one
+ * needs a secondary relation too.
+ */
+ CommandCounterIncrement();
+ AlterTableCreateToastTable(relOid);
+ }
+ else
+ {
+ /* Recurse for anything else */
+ ProcessUtility(stmt,
+ queryString,
+ params,
+ false,
+ None_Receiver,
+ NULL);
+ }
+
+ /* Need CCI between commands */
+ if (lnext(l) != NULL)
+ CommandCounterIncrement();
+ }
}
break;
@@ -693,7 +723,40 @@ ProcessUtility(Node *parsetree,
break;
case T_AlterTableStmt:
- AlterTable((AlterTableStmt *) parsetree);
+ {
+ List *stmts;
+ ListCell *l;
+
+ /* Run parse analysis ... */
+ stmts = transformAlterTableStmt((AlterTableStmt *) parsetree,
+ queryString);
+
+ /* ... and do it */
+ foreach(l, stmts)
+ {
+ Node *stmt = (Node *) lfirst(l);
+
+ if (IsA(stmt, AlterTableStmt))
+ {
+ /* Do the table alteration proper */
+ AlterTable((AlterTableStmt *) stmt);
+ }
+ else
+ {
+ /* Recurse for anything else */
+ ProcessUtility(stmt,
+ queryString,
+ params,
+ false,
+ None_Receiver,
+ NULL);
+ }
+
+ /* Need CCI between commands */
+ if (lnext(l) != NULL)
+ CommandCounterIncrement();
+ }
+ }
break;
case T_AlterDomainStmt:
@@ -812,7 +875,7 @@ ProcessUtility(Node *parsetree,
CheckRelationOwnership(stmt->relation, true);
/* Run parse analysis ... */
- stmt = analyzeIndexStmt(stmt, queryString);
+ stmt = transformIndexStmt(stmt, queryString);
/* ... and do it */
DefineIndex(stmt->relation, /* relation */
@@ -1605,7 +1668,7 @@ CreateCommandTag(Node *parsetree)
/*
* We might be supporting ALTER INDEX here, so set the
- * completion table appropriately. Catch all other
+ * completion tag appropriately. Catch all other
* possibilities with ALTER TABLE
*/
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index b500024e932..97ee524a0b2 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/makefuncs.h,v 1.58 2007/03/17 00:11:05 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/makefuncs.h,v 1.59 2007/06/23 22:12:52 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -36,6 +36,8 @@ extern TargetEntry *makeTargetEntry(Expr *expr,
extern TargetEntry *flatCopyTargetEntry(TargetEntry *src_tle);
+extern FromExpr *makeFromExpr(List *fromlist, Node *quals);
+
extern Const *makeConst(Oid consttype,
int32 consttypmod,
int constlen,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 179d7a765db..50bb6c2048f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.348 2007/04/27 22:05:49 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.349 2007/06/23 22:12:52 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -21,7 +21,7 @@
typedef enum QuerySource
{
QSRC_ORIGINAL, /* original parsetree (explicit query) */
- QSRC_PARSER, /* added by parse analysis */
+ QSRC_PARSER, /* added by parse analysis (now unused) */
QSRC_INSTEAD_RULE, /* added by unconditional INSTEAD rule */
QSRC_QUAL_INSTEAD_RULE, /* added by conditional INSTEAD rule */
QSRC_NON_INSTEAD_RULE /* added by non-INSTEAD rule */
@@ -799,10 +799,12 @@ typedef struct SetOperationStmt
/*****************************************************************************
* Other Statements (no optimizations required)
*
- * Some of them require a little bit of transformation (which is also
- * done by transformStmt). The whole structure is then passed on to
- * ProcessUtility (by-passing the optimization step) as the utilityStmt
- * field in Query.
+ * These are not touched by parser/analyze.c except to put them into
+ * the utilityStmt field of a Query. This is eventually passed to
+ * ProcessUtility (by-passing rewriting and planning). Some of the
+ * statements do need attention from parse analysis, and this is
+ * done by routines in parser/parse_utilcmd.c after ProcessUtility
+ * receives the command for execution.
*****************************************************************************/
/*
@@ -886,7 +888,7 @@ typedef enum AlterTableType
AT_ReAddIndex, /* internal to commands/tablecmds.c */
AT_AddConstraint, /* add constraint */
AT_ProcessedConstraint, /* pre-processed add constraint (local in
- * parser/analyze.c) */
+ * parser/parse_utilcmd.c) */
AT_DropConstraint, /* drop constraint */
AT_DropConstraintQuietly, /* drop constraint, no error/warning (local in
* commands/tablecmds.c) */
@@ -1083,7 +1085,7 @@ typedef struct CreateStmt
* relation). We should never have both in the same node!
*
* Constraint attributes (DEFERRABLE etc) are initially represented as
- * separate Constraint nodes for simplicity of parsing. analyze.c makes
+ * separate Constraint nodes for simplicity of parsing. parse_utilcmd.c makes
* a pass through the constraints list to attach the info to the appropriate
* FkConstraint node (and, perhaps, someday to other kinds of constraints).
* ----------
diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h
index 033dce60462..5087d20a60a 100644
--- a/src/include/parser/analyze.h
+++ b/src/include/parser/analyze.h
@@ -1,12 +1,13 @@
/*-------------------------------------------------------------------------
*
* analyze.h
+ * parse analysis for optimizable statements
*
*
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/parser/analyze.h,v 1.36 2007/03/13 00:33:43 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/parser/analyze.h,v 1.37 2007/06/23 22:12:52 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -16,16 +17,14 @@
#include "parser/parse_node.h"
-extern List *parse_analyze(Node *parseTree, const char *sourceText,
+extern Query *parse_analyze(Node *parseTree, const char *sourceText,
Oid *paramTypes, int numParams);
-extern List *parse_analyze_varparams(Node *parseTree, const char *sourceText,
+extern Query *parse_analyze_varparams(Node *parseTree, const char *sourceText,
Oid **paramTypes, int *numParams);
-extern List *parse_sub_analyze(Node *parseTree, ParseState *parentParseState);
-extern IndexStmt *analyzeIndexStmt(IndexStmt *stmt, const char *queryString);
-extern void analyzeRuleStmt(RuleStmt *stmt, const char *queryString,
- List **actions, Node **whereClause);
-extern List *analyzeCreateSchemaStmt(CreateSchemaStmt *stmt);
+extern Query *parse_sub_analyze(Node *parseTree, ParseState *parentParseState);
+extern Query *transformStmt(ParseState *pstate, Node *parseTree);
+
extern void CheckSelectLocking(Query *qry);
extern void applyLockingClause(Query *qry, Index rtindex,
bool forUpdate, bool noWait);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 7382a4f531c..a1d84801fc5 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/parser/parse_node.h,v 1.51 2007/01/05 22:19:57 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/parser/parse_node.h,v 1.52 2007/06/23 22:12:52 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -83,6 +83,7 @@ typedef struct ParseState
} ParseState;
extern ParseState *make_parsestate(ParseState *parentParseState);
+extern void free_parsestate(ParseState *pstate);
extern int parser_errposition(ParseState *pstate, int location);
extern Var *make_var(ParseState *pstate, RangeTblEntry *rte, int attrno);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
new file mode 100644
index 00000000000..f9ca398e253
--- /dev/null
+++ b/src/include/parser/parse_utilcmd.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * parse_utilcmd.h
+ * parse analysis for utility commands
+ *
+ *
+ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL: pgsql/src/include/parser/parse_utilcmd.h,v 1.1 2007/06/23 22:12:52 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARSE_UTILCMD_H
+#define PARSE_UTILCMD_H
+
+#include "parser/parse_node.h"
+
+
+extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
+extern List *transformAlterTableStmt(AlterTableStmt *stmt,
+ const char *queryString);
+extern IndexStmt *transformIndexStmt(IndexStmt *stmt, const char *queryString);
+extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
+ List **actions, Node **whereClause);
+extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
+
+#endif /* PARSE_UTILCMD_H */