Commit 46379d6e authored by Tom Lane's avatar Tom Lane

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).
parent ec0bb02d
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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, ...@@ -367,7 +367,7 @@ DefineIndex(RangeVar *heapRelation,
/* /*
* This shouldn't happen during CREATE TABLE, but can happen * This shouldn't happen during CREATE TABLE, but can happen
* during ALTER TABLE. Keep message in sync with * during ALTER TABLE. Keep message in sync with
* transformIndexConstraints() in parser/analyze.c. * transformIndexConstraints() in parser/parse_utilcmd.c.
*/ */
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN), (errcode(ERRCODE_UNDEFINED_COLUMN),
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
* Copyright (c) 2002-2007, PostgreSQL Global Development Group * Copyright (c) 2002-2007, PostgreSQL Global Development Group
* *
* IDENTIFICATION * 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) ...@@ -55,7 +55,6 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
{ {
Oid *argtypes = NULL; Oid *argtypes = NULL;
int nargs; int nargs;
List *queries;
Query *query; Query *query;
List *query_list, List *query_list,
*plan_list; *plan_list;
...@@ -105,9 +104,9 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString) ...@@ -105,9 +104,9 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
* Because parse analysis scribbles on the raw querytree, we must make * 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. * a copy to ensure we have a pristine raw tree to cache. FIXME someday.
*/ */
queries = parse_analyze_varparams((Node *) copyObject(stmt->query), query = parse_analyze_varparams((Node *) copyObject(stmt->query),
queryString, queryString,
&argtypes, &nargs); &argtypes, &nargs);
/* /*
* Check that all parameter types were determined. * Check that all parameter types were determined.
...@@ -124,15 +123,8 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString) ...@@ -124,15 +123,8 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
} }
/* /*
* Shouldn't get any extra statements, since grammar only allows * grammar only allows OptimizableStmt, so this check should be redundant
* OptimizableStmt
*/ */
if (list_length(queries) != 1)
elog(ERROR, "unexpected extra stuff in prepared statement");
query = (Query *) linitial(queries);
Assert(IsA(query, Query));
switch (query->commandType) switch (query->commandType)
{ {
case CMD_SELECT: case CMD_SELECT:
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 @@ ...@@ -24,7 +24,7 @@
#include "commands/dbcommands.h" #include "commands/dbcommands.h"
#include "commands/schemacmds.h" #include "commands/schemacmds.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "parser/analyze.h" #include "parser/parse_utilcmd.h"
#include "tcop/utility.h" #include "tcop/utility.h"
#include "utils/acl.h" #include "utils/acl.h"
#include "utils/builtins.h" #include "utils/builtins.h"
...@@ -111,39 +111,31 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString) ...@@ -111,39 +111,31 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
/* /*
* Examine the list of commands embedded in the CREATE SCHEMA command, and * Examine the list of commands embedded in the CREATE SCHEMA command, and
* reorganize them into a sequentially executable order with no forward * reorganize them into a sequentially executable order with no forward
* references. Note that the result is still a list of raw parsetrees in * references. Note that the result is still a list of raw parsetrees
* need of parse analysis --- we cannot, in general, run analyze.c on one * --- we cannot, in general, run parse analysis on one statement until
* statement until we have actually executed the prior ones. * 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) foreach(parsetree_item, parsetree_list)
{ {
Node *parsetree = (Node *) lfirst(parsetree_item); Node *stmt = (Node *) lfirst(parsetree_item);
List *querytree_list;
ListCell *querytree_item; /* do this step */
ProcessUtility(stmt,
querytree_list = parse_analyze(parsetree, queryString, NULL, 0); queryString,
NULL,
foreach(querytree_item, querytree_list) false, /* not top level */
{ None_Receiver,
Query *querytree = (Query *) lfirst(querytree_item); NULL);
/* make sure later steps can see the object created here */
/* schemas should contain only utility stmts */ CommandCounterIncrement();
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();
}
} }
/* Reset search path to normal state */ /* Reset search path to normal state */
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 @@ ...@@ -44,7 +44,6 @@
#include "optimizer/clauses.h" #include "optimizer/clauses.h"
#include "optimizer/plancat.h" #include "optimizer/plancat.h"
#include "optimizer/prep.h" #include "optimizer/prep.h"
#include "parser/analyze.h"
#include "parser/gramparse.h" #include "parser/gramparse.h"
#include "parser/parse_clause.h" #include "parser/parse_clause.h"
#include "parser/parse_coerce.h" #include "parser/parse_coerce.h"
...@@ -52,6 +51,7 @@ ...@@ -52,6 +51,7 @@
#include "parser/parse_oper.h" #include "parser/parse_oper.h"
#include "parser/parse_relation.h" #include "parser/parse_relation.h"
#include "parser/parse_type.h" #include "parser/parse_type.h"
#include "parser/parse_utilcmd.h"
#include "parser/parser.h" #include "parser/parser.h"
#include "rewrite/rewriteDefine.h" #include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteHandler.h" #include "rewrite/rewriteHandler.h"
...@@ -394,7 +394,7 @@ DefineRelation(CreateStmt *stmt, char relkind) ...@@ -394,7 +394,7 @@ DefineRelation(CreateStmt *stmt, char relkind)
add_nonduplicate_constraint(cdef, check, &ncheck); 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 * due to LIKE tab INCLUDING CONSTRAINTS
*/ */
foreach(listptr, stmt->constraints) foreach(listptr, stmt->constraints)
...@@ -2922,7 +2922,7 @@ find_composite_type_dependencies(Oid typeOid, ...@@ -2922,7 +2922,7 @@ find_composite_type_dependencies(Oid typeOid,
* *
* Adds an additional attribute to a relation making the assumption that * Adds an additional attribute to a relation making the assumption that
* CHECK, NOT NULL, and FOREIGN KEY constraints will be removed from the * 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. * AlterTableCmd's.
*/ */
static void static void
...@@ -3745,9 +3745,9 @@ ATExecDropColumn(Relation rel, const char *colName, ...@@ -3745,9 +3745,9 @@ ATExecDropColumn(Relation rel, const char *colName,
/* /*
* ALTER TABLE ADD INDEX * ALTER TABLE ADD INDEX
* *
* There is no such command in the grammar, but the parser converts UNIQUE * There is no such command in the grammar, but parse_utilcmd.c converts
* and PRIMARY KEY constraints into AT_AddIndex subcommands. This lets us * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands. This lets
* schedule creation of the index at the appropriate time during ALTER. * us schedule creation of the index at the appropriate time during ALTER.
*/ */
static void static void
ATExecAddIndex(AlteredTableInfo *tab, Relation rel, ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
...@@ -3766,13 +3766,8 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel, ...@@ -3766,13 +3766,8 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
/* suppress notices when rebuilding existing index */ /* suppress notices when rebuilding existing index */
quiet = is_rebuild; quiet = is_rebuild;
/* /* The IndexStmt has already been through transformIndexStmt */
* 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);
/* ... and do it */
DefineIndex(stmt->relation, /* relation */ DefineIndex(stmt->relation, /* relation */
stmt->idxname, /* index name */ stmt->idxname, /* index name */
InvalidOid, /* no predefined OID */ InvalidOid, /* no predefined OID */
...@@ -3806,7 +3801,7 @@ ATExecAddConstraint(AlteredTableInfo *tab, Relation rel, Node *newConstraint) ...@@ -3806,7 +3801,7 @@ ATExecAddConstraint(AlteredTableInfo *tab, Relation rel, Node *newConstraint)
/* /*
* Currently, we only expect to see CONSTR_CHECK nodes * Currently, we only expect to see CONSTR_CHECK nodes
* arriving here (see the preprocessing done in * 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. * to add more code later.
*/ */
switch (constr->contype) switch (constr->contype)
...@@ -5239,17 +5234,27 @@ ATPostAlterTypeParse(char *cmd, List **wqueue) ...@@ -5239,17 +5234,27 @@ ATPostAlterTypeParse(char *cmd, List **wqueue)
ListCell *list_item; ListCell *list_item;
/* /*
* We expect that we only have to do raw parsing and parse analysis, not * We expect that we will get only ALTER TABLE and CREATE INDEX statements.
* any rule rewriting, since these will all be utility 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); raw_parsetree_list = raw_parser(cmd);
querytree_list = NIL; querytree_list = NIL;
foreach(list_item, raw_parsetree_list) foreach(list_item, raw_parsetree_list)
{ {
Node *parsetree = (Node *) lfirst(list_item); Node *stmt = (Node *) lfirst(list_item);
querytree_list = list_concat(querytree_list, if (IsA(stmt, IndexStmt))
parse_analyze(parsetree, cmd, NULL, 0)); 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) ...@@ -5258,17 +5263,15 @@ ATPostAlterTypeParse(char *cmd, List **wqueue)
*/ */
foreach(list_item, querytree_list) foreach(list_item, querytree_list)
{ {
Query *query = (Query *) lfirst(list_item); Node *stm = (Node *) lfirst(list_item);
Relation rel; Relation rel;
AlteredTableInfo *tab; AlteredTableInfo *tab;
Assert(IsA(query, Query)); switch (nodeTag(stm))
Assert(query->commandType == CMD_UTILITY);
switch (nodeTag(query->utilityStmt))
{ {
case T_IndexStmt: case T_IndexStmt:
{ {
IndexStmt *stmt = (IndexStmt *) query->utilityStmt; IndexStmt *stmt = (IndexStmt *) stm;
AlterTableCmd *newcmd; AlterTableCmd *newcmd;
rel = relation_openrv(stmt->relation, AccessExclusiveLock); rel = relation_openrv(stmt->relation, AccessExclusiveLock);
...@@ -5283,7 +5286,7 @@ ATPostAlterTypeParse(char *cmd, List **wqueue) ...@@ -5283,7 +5286,7 @@ ATPostAlterTypeParse(char *cmd, List **wqueue)
} }
case T_AlterTableStmt: case T_AlterTableStmt:
{ {
AlterTableStmt *stmt = (AlterTableStmt *) query->utilityStmt; AlterTableStmt *stmt = (AlterTableStmt *) stm;
ListCell *lcmd; ListCell *lcmd;
rel = relation_openrv(stmt->relation, AccessExclusiveLock); rel = relation_openrv(stmt->relation, AccessExclusiveLock);
...@@ -5313,7 +5316,7 @@ ATPostAlterTypeParse(char *cmd, List **wqueue) ...@@ -5313,7 +5316,7 @@ ATPostAlterTypeParse(char *cmd, List **wqueue)
} }
default: default:
elog(ERROR, "unexpected statement type: %d", elog(ERROR, "unexpected statement type: %d",
(int) nodeTag(query->utilityStmt)); (int) nodeTag(stm));
} }
} }
} }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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) ...@@ -351,7 +351,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
void void
DefineView(ViewStmt *stmt, const char *queryString) DefineView(ViewStmt *stmt, const char *queryString)
{ {
List *stmts;
Query *viewParse; Query *viewParse;
Oid viewOid; Oid viewOid;
RangeVar *view; RangeVar *view;
...@@ -363,15 +362,12 @@ DefineView(ViewStmt *stmt, const char *queryString) ...@@ -363,15 +362,12 @@ DefineView(ViewStmt *stmt, const char *queryString)
* Since parse analysis scribbles on its input, copy the raw parse tree; * Since parse analysis scribbles on its input, copy the raw parse tree;
* this ensures we don't corrupt a prepared statement, for example. * this ensures we don't corrupt a prepared statement, for example.
*/ */
stmts = parse_analyze((Node *) copyObject(stmt->query), viewParse = parse_analyze((Node *) copyObject(stmt->query),
queryString, NULL, 0); queryString, NULL, 0);
/* /*
* The grammar should ensure that the result is a single SELECT Query. * 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) || if (!IsA(viewParse, Query) ||
viewParse->commandType != CMD_SELECT) viewParse->commandType != CMD_SELECT)
elog(ERROR, "unexpected parse analysis result"); elog(ERROR, "unexpected parse analysis result");
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* *
* *
* IDENTIFICATION * 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 $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -134,6 +134,20 @@ flatCopyTargetEntry(TargetEntry *src_tle) ...@@ -134,6 +134,20 @@ flatCopyTargetEntry(TargetEntry *src_tle)
return tle; return 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 - * makeConst -
* creates a Const node * creates a Const node
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 * HISTORY
* AUTHOR DATE MAJOR EVENT * AUTHOR DATE MAJOR EVENT
...@@ -2910,7 +2910,6 @@ inline_function(Oid funcid, Oid result_type, List *args, ...@@ -2910,7 +2910,6 @@ inline_function(Oid funcid, Oid result_type, List *args,
MemoryContext mycxt; MemoryContext mycxt;
ErrorContextCallback sqlerrcontext; ErrorContextCallback sqlerrcontext;
List *raw_parsetree_list; List *raw_parsetree_list;
List *querytree_list;
Query *querytree; Query *querytree;
Node *newexpr; Node *newexpr;
int *usecounts; int *usecounts;
...@@ -2986,13 +2985,8 @@ inline_function(Oid funcid, Oid result_type, List *args, ...@@ -2986,13 +2985,8 @@ inline_function(Oid funcid, Oid result_type, List *args,
if (list_length(raw_parsetree_list) != 1) if (list_length(raw_parsetree_list) != 1)
goto fail; goto fail;
querytree_list = parse_analyze(linitial(raw_parsetree_list), src, querytree = parse_analyze(linitial(raw_parsetree_list), src,
argtypes, funcform->pronargs); argtypes, funcform->pronargs);
if (list_length(querytree_list) != 1)
goto fail;
querytree = (Query *) linitial(querytree_list);
/* /*
* The single command must be a simple "SELECT expression". * The single command must be a simple "SELECT expression".
...@@ -3025,7 +3019,7 @@ inline_function(Oid funcid, Oid result_type, List *args, ...@@ -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 * no rewriting was needed; that's probably not important, but let's be
* careful. * 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 */ goto fail; /* reject whole-tuple-result cases */
/* /*
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# #
# Makefile for parser # 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) ...@@ -14,7 +14,7 @@ override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
OBJS= analyze.o gram.o keywords.o parser.o parse_agg.o parse_clause.o \ 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_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 FLEXFLAGS = -CF
......
This directory does more than tokenize and parse SQL queries. It also 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. to the optimizer and then executor.
parser.c things start here parser.c things start here
scan.l break query into tokens 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 keywords.c turn keywords into specific tokens
gram.y parse the tokens and fill query-type-specific structures 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_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_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_agg.c handle aggregates, like SUM(col1), AVG(col2), ...
parse_func.c handle functions, table.column and column identifiers parse_func.c handle functions, table.column and column identifiers
parse_node.c create nodes for various structures parse_node.c create nodes for various structures
parse_target.c handle the result list of the query parse_target.c handle the result list of the query
parse_relation.c support routines for tables and column handling parse_relation.c support routines for tables and column handling
parse_type.c support routines for type handling parse_type.c support routines for type handling
parse_utilcmd.c parse analysis for utility commands (done at execution time)
...@@ -10,92 +10,34 @@ ...@@ -10,92 +10,34 @@
* utility commands, no locks are obtained here (and if they were, we could * 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 * 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. * for utility commands is to just dump them into a Query node untransformed.
* parse_analyze does do some purely syntactic transformations on CREATE TABLE * DECLARE CURSOR and EXPLAIN are exceptions because they contain
* and ALTER TABLE, but that's about it. In cases where this module contains * optimizable statements.
* 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.
* *
* *
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 "postgres.h"
#include "access/heapam.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/pg_type.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 "nodes/makefuncs.h"
#include "optimizer/clauses.h" #include "optimizer/clauses.h"
#include "optimizer/var.h" #include "optimizer/var.h"
#include "parser/analyze.h" #include "parser/analyze.h"
#include "parser/gramparse.h"
#include "parser/parse_agg.h" #include "parser/parse_agg.h"
#include "parser/parse_clause.h" #include "parser/parse_clause.h"
#include "parser/parse_coerce.h" #include "parser/parse_coerce.h"
#include "parser/parse_expr.h" #include "parser/parse_expr.h"
#include "parser/parse_relation.h" #include "parser/parse_relation.h"
#include "parser/parse_target.h" #include "parser/parse_target.h"
#include "parser/parse_type.h"
#include "parser/parsetree.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 typedef struct
{ {
Oid *paramTypes; Oid *paramTypes;
...@@ -103,50 +45,24 @@ typedef struct ...@@ -103,50 +45,24 @@ typedef struct
} check_parameter_resolution_context; } 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 *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt, static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
List **extras_before, List **extras_after);
static List *transformInsertRow(ParseState *pstate, List *exprlist, static List *transformInsertRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos); List *stmtcols, List *icolumns, List *attrnos);
static List *transformReturningList(ParseState *pstate, List *returningList);
static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt); static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt); static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt); static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt); static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt);
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 Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static List *transformReturningList(ParseState *pstate, List *returningList);
static Query *transformDeclareCursorStmt(ParseState *pstate, static Query *transformDeclareCursorStmt(ParseState *pstate,
DeclareCursorStmt *stmt); DeclareCursorStmt *stmt);
static Query *transformExplainStmt(ParseState *pstate, static Query *transformExplainStmt(ParseState *pstate,
ExplainStmt *stmt); 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 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, static bool check_parameter_resolution_walker(Node *node,
check_parameter_resolution_context *context); check_parameter_resolution_context *context);
...@@ -161,28 +77,27 @@ static bool check_parameter_resolution_walker(Node *node, ...@@ -161,28 +77,27 @@ static bool check_parameter_resolution_walker(Node *node,
* Optionally, information about $n parameter types can be supplied. * Optionally, information about $n parameter types can be supplied.
* References to $n indexes not defined by paramTypes[] are disallowed. * 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 * The result is a Query node. Optimizable statements require considerable
* produce multiple Queries). Optimizable statements require considerable * transformation, while utility-type statements are simply hung off
* transformation, while most utility-type statements are simply hung off
* a dummy CMD_UTILITY Query node. * a dummy CMD_UTILITY Query node.
*/ */
List * Query *
parse_analyze(Node *parseTree, const char *sourceText, parse_analyze(Node *parseTree, const char *sourceText,
Oid *paramTypes, int numParams) Oid *paramTypes, int numParams)
{ {
ParseState *pstate = make_parsestate(NULL); ParseState *pstate = make_parsestate(NULL);
List *result; Query *query;
pstate->p_sourcetext = sourceText; pstate->p_sourcetext = sourceText;
pstate->p_paramtypes = paramTypes; pstate->p_paramtypes = paramTypes;
pstate->p_numparams = numParams; pstate->p_numparams = numParams;
pstate->p_variableparams = false; 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, ...@@ -192,24 +107,24 @@ parse_analyze(Node *parseTree, const char *sourceText,
* symbol datatypes from context. The passed-in paramTypes[] array can * symbol datatypes from context. The passed-in paramTypes[] array can
* be modified or enlarged (via repalloc). * be modified or enlarged (via repalloc).
*/ */
List * Query *
parse_analyze_varparams(Node *parseTree, const char *sourceText, parse_analyze_varparams(Node *parseTree, const char *sourceText,
Oid **paramTypes, int *numParams) Oid **paramTypes, int *numParams)
{ {
ParseState *pstate = make_parsestate(NULL); ParseState *pstate = make_parsestate(NULL);
List *result; Query *query;
pstate->p_sourcetext = sourceText; pstate->p_sourcetext = sourceText;
pstate->p_paramtypes = *paramTypes; pstate->p_paramtypes = *paramTypes;
pstate->p_numparams = *numParams; pstate->p_numparams = *numParams;
pstate->p_variableparams = true; pstate->p_variableparams = true;
result = do_parse_analyze(parseTree, pstate); query = transformStmt(pstate, parseTree);
*paramTypes = pstate->p_paramtypes; *paramTypes = pstate->p_paramtypes;
*numParams = pstate->p_numparams; *numParams = pstate->p_numparams;
pfree(pstate); free_parsestate(pstate);
/* make sure all is well with parameter types */ /* make sure all is well with parameter types */
if (*numParams > 0) if (*numParams > 0)
...@@ -218,100 +133,37 @@ parse_analyze_varparams(Node *parseTree, const char *sourceText, ...@@ -218,100 +133,37 @@ parse_analyze_varparams(Node *parseTree, const char *sourceText,
context.paramTypes = *paramTypes; context.paramTypes = *paramTypes;
context.numParams = *numParams; context.numParams = *numParams;
check_parameter_resolution_walker((Node *) result, &context); check_parameter_resolution_walker((Node *) query, &context);
} }
return result; return query;
} }
/* /*
* parse_sub_analyze * parse_sub_analyze
* Entry point for recursively analyzing a sub-statement. * Entry point for recursively analyzing a sub-statement.
*/ */
List * Query *
parse_sub_analyze(Node *parseTree, ParseState *parentParseState) parse_sub_analyze(Node *parseTree, ParseState *parentParseState)
{ {
ParseState *pstate = make_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; Query *query;
ListCell *l;
query = transformStmt(pstate, parseTree, &extras_before, &extras_after);
/* don't need to access result relation any more */ query = transformStmt(pstate, parseTree);
release_pstate_resources(pstate);
foreach(l, extras_before) free_parsestate(pstate);
result = list_concat(result, parse_sub_analyze(lfirst(l), pstate));
result = lappend(result, query); return 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;
}
}
return result;
}
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;
} }
/* /*
* transformStmt - * transformStmt -
* transform a Parse tree into a Query tree. * transform a Parse tree into a Query tree.
*/ */
static Query * Query *
transformStmt(ParseState *pstate, Node *parseTree, transformStmt(ParseState *pstate, Node *parseTree)
List **extras_before, List **extras_after)
{ {
Query *result = NULL; Query *result;
switch (nodeTag(parseTree)) switch (nodeTag(parseTree))
{ {
...@@ -319,8 +171,7 @@ transformStmt(ParseState *pstate, Node *parseTree, ...@@ -319,8 +171,7 @@ transformStmt(ParseState *pstate, Node *parseTree,
* Optimizable statements * Optimizable statements
*/ */
case T_InsertStmt: case T_InsertStmt:
result = transformInsertStmt(pstate, (InsertStmt *) parseTree, result = transformInsertStmt(pstate, (InsertStmt *) parseTree);
extras_before, extras_after);
break; break;
case T_DeleteStmt: case T_DeleteStmt:
...@@ -344,20 +195,6 @@ transformStmt(ParseState *pstate, Node *parseTree, ...@@ -344,20 +195,6 @@ transformStmt(ParseState *pstate, Node *parseTree,
} }
break; 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 * Special cases
*/ */
...@@ -387,17 +224,6 @@ transformStmt(ParseState *pstate, Node *parseTree, ...@@ -387,17 +224,6 @@ transformStmt(ParseState *pstate, Node *parseTree,
result->querySource = QSRC_ORIGINAL; result->querySource = QSRC_ORIGINAL;
result->canSetTag = true; 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; return result;
} }
...@@ -450,8 +276,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) ...@@ -450,8 +276,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
* transform an Insert Statement * transform an Insert Statement
*/ */
static Query * static Query *
transformInsertStmt(ParseState *pstate, InsertStmt *stmt, transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
List **extras_before, List **extras_after)
{ {
Query *qry = makeNode(Query); Query *qry = makeNode(Query);
SelectStmt *selectStmt = (SelectStmt *) stmt->selectStmt; SelectStmt *selectStmt = (SelectStmt *) stmt->selectStmt;
...@@ -552,15 +377,9 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt, ...@@ -552,15 +377,9 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
sub_pstate->p_relnamespace = sub_relnamespace; sub_pstate->p_relnamespace = sub_relnamespace;
sub_pstate->p_varnamespace = sub_varnamespace; sub_pstate->p_varnamespace = sub_varnamespace;
/* selectQuery = transformStmt(sub_pstate, stmt->selectStmt);
* 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);
release_pstate_resources(sub_pstate); free_parsestate(sub_pstate);
pfree(sub_pstate);
/* The grammar should have produced a SELECT, but it might have INTO */ /* The grammar should have produced a SELECT, but it might have INTO */
Assert(IsA(selectQuery, Query)); Assert(IsA(selectQuery, Query));
...@@ -776,1275 +595,77 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt, ...@@ -776,1275 +595,77 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
/* done building the range table and jointree */ /* done building the range table and jointree */
qry->rtable = pstate->p_rtable; qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL); qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
qry->hasSubLinks = pstate->p_hasSubLinks;
/* aggregates not allowed (but subselects are okay) */
if (pstate->p_hasAggs)
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in VALUES")));
return qry;
}
/*
* Prepare an INSERT row for assignment to the target table.
*
* The row might be either a VALUES row, or variables referencing a
* sub-SELECT output.
*/
static List *
transformInsertRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos)
{
List *result;
ListCell *lc;
ListCell *icols;
ListCell *attnos;
/*
* Check length of expr list. It must not have more expressions than
* there are target columns. We allow fewer, but only if no explicit
* columns list was given (the remaining columns are implicitly
* defaulted). Note we must check this *after* transformation because
* that could expand '*' into multiple items.
*/
if (list_length(exprlist) > list_length(icolumns))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("INSERT has more expressions than target columns")));
if (stmtcols != NIL &&
list_length(exprlist) < list_length(icolumns))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("INSERT has more target columns than expressions")));
/*
* Prepare columns for assignment to target table.
*/
result = NIL;
icols = list_head(icolumns);
attnos = list_head(attrnos);
foreach(lc, exprlist)
{
Expr *expr = (Expr *) lfirst(lc);
ResTarget *col;
col = (ResTarget *) lfirst(icols);
Assert(IsA(col, ResTarget));
expr = transformAssignedExpr(pstate, expr,
col->name,
lfirst_int(attnos),
col->indirection,
col->location);
result = lappend(result, expr);
icols = lnext(icols);
attnos = lnext(attnos);
}
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")));
qry->hasSubLinks = pstate->p_hasSubLinks;
/* aggregates not allowed (but subselects are okay) */ /* aggregates not allowed (but subselects are okay) */
if (pstate->p_hasAggs) if (pstate->p_hasAggs)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR), (errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in rule WHERE condition"))); errmsg("cannot use aggregate function in VALUES")));
return qry;
}
/*
* Prepare an INSERT row for assignment to the target table.
*
* The row might be either a VALUES row, or variables referencing a
* sub-SELECT output.
*/
static List *
transformInsertRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos)
{
List *result;
ListCell *lc;
ListCell *icols;
ListCell *attnos;
/* /*
* 'instead nothing' rules with a qualification need a query rangetable so * Check length of expr list. It must not have more expressions than
* the rewrite handler can add the negated rule qualification to the * there are target columns. We allow fewer, but only if no explicit
* original query. We create a query with the new command type CMD_NOTHING * columns list was given (the remaining columns are implicitly
* here that is treated specially by the rewrite system. * defaulted). Note we must check this *after* transformation because
* that could expand '*' into multiple items.
*/ */
if (stmt->actions == NIL) if (list_length(exprlist) > list_length(icolumns))
{ ereport(ERROR,
Query *nothing_qry = makeNode(Query); (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("INSERT has more expressions than target columns")));
nothing_qry->commandType = CMD_NOTHING; if (stmtcols != NIL &&
nothing_qry->rtable = pstate->p_rtable; list_length(exprlist) < list_length(icolumns))
nothing_qry->jointree = makeFromExpr(NIL, NULL); /* no join wanted */ ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("INSERT has more target columns than expressions")));
*actions = list_make1(nothing_qry); /*
} * Prepare columns for assignment to target table.
else */
result = NIL;
icols = list_head(icolumns);
attnos = list_head(attrnos);
foreach(lc, exprlist)
{ {
ListCell *l; Expr *expr = (Expr *) lfirst(lc);
List *newactions = NIL; ResTarget *col;
/*
* 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;
}
/* col = (ResTarget *) lfirst(icols);
* For efficiency's sake, add OLD to the rule action's jointree Assert(IsA(col, ResTarget));
* 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); expr = transformAssignedExpr(pstate, expr,
newactions = lappend(newactions, top_subqry); col->name,
newactions = list_concat(newactions, extras_after); lfirst_int(attnos),
col->indirection,
col->location);
release_pstate_resources(sub_pstate); result = lappend(result, expr);
pfree(sub_pstate);
}
*actions = newactions; icols = lnext(icols);
attnos = lnext(attnos);
} }
release_pstate_resources(pstate); return result;
pfree(pstate);
/* Close relation, but keep the exclusive lock */
heap_close(rel, NoLock);
} }
...@@ -2052,7 +673,8 @@ analyzeRuleStmt(RuleStmt *stmt, const char *queryString, ...@@ -2052,7 +673,8 @@ analyzeRuleStmt(RuleStmt *stmt, const char *queryString,
* transformSelectStmt - * transformSelectStmt -
* transforms a Select Statement * 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 * static Query *
transformSelectStmt(ParseState *pstate, SelectStmt *stmt) transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
...@@ -2323,7 +945,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt) ...@@ -2323,7 +945,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
} }
/* /*
* transformSetOperationsStmt - * transformSetOperationStmt -
* transforms a set-operations tree * transforms a set-operations tree
* *
* A set-operation tree is just a SELECT, but with UNION/INTERSECT/EXCEPT * A set-operation tree is just a SELECT, but with UNION/INTERSECT/EXCEPT
...@@ -2591,7 +1213,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt) ...@@ -2591,7 +1213,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
if (isLeaf) if (isLeaf)
{ {
/* Process leaf SELECT */ /* Process leaf SELECT */
List *selectList;
Query *selectQuery; Query *selectQuery;
char selectName[32]; char selectName[32];
RangeTblEntry *rte; RangeTblEntry *rte;
...@@ -2604,11 +1225,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt) ...@@ -2604,11 +1225,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
* of this sub-query, because they are not in the toplevel pstate's * of this sub-query, because they are not in the toplevel pstate's
* namespace list. * namespace list.
*/ */
selectList = parse_sub_analyze((Node *) stmt, pstate); selectQuery = parse_sub_analyze((Node *) stmt, pstate);
Assert(list_length(selectList) == 1);
selectQuery = (Query *) linitial(selectList);
Assert(IsA(selectQuery, Query));
/* /*
* Check for bogus references to Vars on the current query level (but * Check for bogus references to Vars on the current query level (but
...@@ -2761,7 +1378,10 @@ getSetColTypes(ParseState *pstate, Node *node, ...@@ -2761,7 +1378,10 @@ getSetColTypes(ParseState *pstate, Node *node,
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(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 static void
applyColumnNames(List *dst, List *src) applyColumnNames(List *dst, List *src)
{ {
...@@ -2957,185 +1577,6 @@ transformReturningList(ParseState *pstate, List *returningList) ...@@ -2957,185 +1577,6 @@ transformReturningList(ParseState *pstate, List *returningList)
return rlist; 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 - * transformDeclareCursorStmt -
...@@ -3152,8 +1593,6 @@ static Query * ...@@ -3152,8 +1593,6 @@ static Query *
transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt) transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
{ {
Query *result; Query *result;
List *extras_before = NIL,
*extras_after = NIL;
/* /*
* Don't allow both SCROLL and NO SCROLL to be specified * Don't allow both SCROLL and NO SCROLL to be specified
...@@ -3164,12 +1603,8 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt) ...@@ -3164,12 +1603,8 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION), (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("cannot specify both SCROLL and NO SCROLL"))); errmsg("cannot specify both SCROLL and NO SCROLL")));
result = transformStmt(pstate, stmt->query, result = transformStmt(pstate, stmt->query);
&extras_before, &extras_after);
/* Shouldn't get any extras, since grammar only allows SelectStmt */
if (extras_before || extras_after)
elog(ERROR, "unexpected extra stuff in cursor statement");
if (!IsA(result, Query) || if (!IsA(result, Query) ||
result->commandType != CMD_SELECT || result->commandType != CMD_SELECT ||
result->utilityStmt != NULL) result->utilityStmt != NULL)
...@@ -3219,12 +1654,8 @@ transformExplainStmt(ParseState *pstate, ExplainStmt *stmt) ...@@ -3219,12 +1654,8 @@ transformExplainStmt(ParseState *pstate, ExplainStmt *stmt)
if (pstate->p_variableparams) if (pstate->p_variableparams)
{ {
List *extras_before = NIL,
*extras_after = NIL;
/* Since parse analysis scribbles on its input, copy the tree first! */ /* Since parse analysis scribbles on its input, copy the tree first! */
(void) transformStmt(pstate, copyObject(stmt->query), (void) transformStmt(pstate, copyObject(stmt->query));
&extras_before, &extras_after);
} }
/* Now return the untransformed command as a utility Query */ /* Now return the untransformed command as a utility Query */
...@@ -3419,281 +1850,6 @@ applyLockingClause(Query *qry, Index rtindex, bool forUpdate, bool noWait) ...@@ -3419,281 +1850,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 * Traverse a fully-analyzed tree to verify that parameter symbols
* match their types. We need this because some Params might still * match their types. We need this because some Params might still
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
* *
* *
* IDENTIFICATION * 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 * HISTORY
* AUTHOR DATE MAJOR EVENT * AUTHOR DATE MAJOR EVENT
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
* SET SQL_inheritance TO off; SELECT * FROM foo; * SET SQL_inheritance TO off; SELECT * FROM foo;
* because the entire string is parsed by gram.y before the SET gets * because the entire string is parsed by gram.y before the SET gets
* executed. Anything that depends on the database or changeable state * 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 * right time not the wrong time. The handling of SQL_inheritance is
* a good example. * a good example.
* *
...@@ -2093,9 +2093,10 @@ ColConstraintElem: ...@@ -2093,9 +2093,10 @@ ColConstraintElem:
* ConstraintAttr represents constraint attributes, which we parse as if * ConstraintAttr represents constraint attributes, which we parse as if
* they were independent constraint clauses, in order to avoid shift/reduce * they were independent constraint clauses, in order to avoid shift/reduce
* conflicts (since NOT might start either an independent NOT NULL clause * conflicts (since NOT might start either an independent NOT NULL clause
* or an attribute). analyze.c is responsible for attaching the attribute * or an attribute). parse_utilcmd.c is responsible for attaching the
* information to the preceding "real" constraint node, and for complaining * attribute information to the preceding "real" constraint node, and for
* if attribute clauses appear in the wrong place or wrong combinations. * complaining if attribute clauses appear in the wrong place or wrong
* combinations.
* *
* See also ConstraintAttributeSpec, which can be used in places where * See also ConstraintAttributeSpec, which can be used in places where
* there is no parsing conflict. * there is no parsing conflict.
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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, ...@@ -152,8 +152,8 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
* Open target rel and grab suitable lock (which we will hold till end of * Open target rel and grab suitable lock (which we will hold till end of
* transaction). * transaction).
* *
* analyze.c will eventually do the corresponding heap_close(), but *not* * free_parsestate() will eventually do the corresponding
* release the lock. * heap_close(), but *not* release the lock.
*/ */
pstate->p_target_relation = heap_openrv(relation, RowExclusiveLock); pstate->p_target_relation = heap_openrv(relation, RowExclusiveLock);
...@@ -193,7 +193,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation, ...@@ -193,7 +193,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
* Simplify InhOption (yes/no/default) into boolean yes/no. * 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 * 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 * we'd do the wrong thing with query strings that intermix SET commands
* with queries. * with queries.
*/ */
...@@ -417,7 +417,6 @@ transformTableEntry(ParseState *pstate, RangeVar *r) ...@@ -417,7 +417,6 @@ transformTableEntry(ParseState *pstate, RangeVar *r)
static RangeTblEntry * static RangeTblEntry *
transformRangeSubselect(ParseState *pstate, RangeSubselect *r) transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
{ {
List *parsetrees;
Query *query; Query *query;
RangeTblEntry *rte; RangeTblEntry *rte;
...@@ -434,19 +433,12 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r) ...@@ -434,19 +433,12 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
/* /*
* Analyze and transform the subquery. * 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 * Check that we got something reasonable. Many of these conditions are
* probably impossible given restrictions of the grammar, but check 'em * impossible given restrictions of the grammar, but check 'em anyway.
* 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 || if (query->commandType != CMD_SELECT ||
query->utilityStmt != NULL) query->utilityStmt != NULL)
elog(ERROR, "expected SELECT query from subquery in FROM"); elog(ERROR, "expected SELECT query from subquery in FROM");
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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) ...@@ -1120,19 +1120,15 @@ transformCaseExpr(ParseState *pstate, CaseExpr *c)
static Node * static Node *
transformSubLink(ParseState *pstate, SubLink *sublink) transformSubLink(ParseState *pstate, SubLink *sublink)
{ {
List *qtrees;
Query *qtree;
Node *result = (Node *) sublink; Node *result = (Node *) sublink;
Query *qtree;
/* If we already transformed this node, do nothing */ /* If we already transformed this node, do nothing */
if (IsA(sublink->subselect, Query)) if (IsA(sublink->subselect, Query))
return result; return result;
pstate->p_hasSubLinks = true; pstate->p_hasSubLinks = true;
qtrees = parse_sub_analyze(sublink->subselect, pstate); qtree = parse_sub_analyze(sublink->subselect, pstate);
if (list_length(qtrees) != 1)
elog(ERROR, "bad query in sub-select");
qtree = (Query *) linitial(qtrees);
if (qtree->commandType != CMD_SELECT || if (qtree->commandType != CMD_SELECT ||
qtree->utilityStmt != NULL || qtree->utilityStmt != NULL ||
qtree->intoClause != NULL) qtree->intoClause != NULL)
......
...@@ -8,12 +8,13 @@ ...@@ -8,12 +8,13 @@
* *
* *
* IDENTIFICATION * 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 "postgres.h"
#include "access/heapam.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "mb/pg_wchar.h" #include "mb/pg_wchar.h"
#include "nodes/makefuncs.h" #include "nodes/makefuncs.h"
...@@ -27,9 +28,11 @@ ...@@ -27,9 +28,11 @@
#include "utils/varbit.h" #include "utils/varbit.h"
/* make_parsestate() /*
* Allocate and initialize a new ParseState. * make_parsestate
* The CALLER is responsible for freeing the ParseState* returned. * Allocate and initialize a new ParseState.
*
* Caller should eventually release the ParseState via free_parsestate().
*/ */
ParseState * ParseState *
make_parsestate(ParseState *parentParseState) make_parsestate(ParseState *parentParseState)
...@@ -52,6 +55,30 @@ make_parsestate(ParseState *parentParseState) ...@@ -52,6 +55,30 @@ make_parsestate(ParseState *parentParseState)
return pstate; 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 * parser_errposition
......
/*-------------------------------------------------------------------------
*
* 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)));
}
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 @@ ...@@ -20,8 +20,8 @@
#include "catalog/pg_rewrite.h" #include "catalog/pg_rewrite.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "optimizer/clauses.h" #include "optimizer/clauses.h"
#include "parser/analyze.h"
#include "parser/parse_expr.h" #include "parser/parse_expr.h"
#include "parser/parse_utilcmd.h"
#include "rewrite/rewriteDefine.h" #include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteManip.h" #include "rewrite/rewriteManip.h"
#include "rewrite/rewriteSupport.h" #include "rewrite/rewriteSupport.h"
...@@ -191,7 +191,7 @@ DefineRule(RuleStmt *stmt, const char *queryString) ...@@ -191,7 +191,7 @@ DefineRule(RuleStmt *stmt, const char *queryString)
Node *whereClause; Node *whereClause;
/* Parse analysis ... */ /* Parse analysis ... */
analyzeRuleStmt(stmt, queryString, &actions, &whereClause); transformRuleStmt(stmt, queryString, &actions, &whereClause);
/* ... and execution */ /* ... and execution */
DefineQueryRewrite(stmt->rulename, DefineQueryRewrite(stmt->rulename,
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 * NOTES
* this is the "main" module of the postgres backend and * this is the "main" module of the postgres backend and
...@@ -165,7 +165,7 @@ static int UseNewLine = 0; /* Use EOF as query delimiters */ ...@@ -165,7 +165,7 @@ static int UseNewLine = 0; /* Use EOF as query delimiters */
static int InteractiveBackend(StringInfo inBuf); static int InteractiveBackend(StringInfo inBuf);
static int SocketBackend(StringInfo inBuf); static int SocketBackend(StringInfo inBuf);
static int ReadCommand(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 bool check_log_statement(List *stmt_list);
static int errdetail_execute(List *raw_parsetree_list); static int errdetail_execute(List *raw_parsetree_list);
static int errdetail_params(ParamListInfo params); static int errdetail_params(ParamListInfo params);
...@@ -567,6 +567,7 @@ List * ...@@ -567,6 +567,7 @@ List *
pg_analyze_and_rewrite(Node *parsetree, const char *query_string, pg_analyze_and_rewrite(Node *parsetree, const char *query_string,
Oid *paramTypes, int numParams) Oid *paramTypes, int numParams)
{ {
Query *query;
List *querytree_list; List *querytree_list;
/* /*
...@@ -575,8 +576,7 @@ pg_analyze_and_rewrite(Node *parsetree, const char *query_string, ...@@ -575,8 +576,7 @@ pg_analyze_and_rewrite(Node *parsetree, const char *query_string,
if (log_parser_stats) if (log_parser_stats)
ResetUsage(); ResetUsage();
querytree_list = parse_analyze(parsetree, query_string, query = parse_analyze(parsetree, query_string, paramTypes, numParams);
paramTypes, numParams);
if (log_parser_stats) if (log_parser_stats)
ShowUsage("PARSE ANALYSIS STATISTICS"); ShowUsage("PARSE ANALYSIS STATISTICS");
...@@ -584,68 +584,55 @@ pg_analyze_and_rewrite(Node *parsetree, const char *query_string, ...@@ -584,68 +584,55 @@ pg_analyze_and_rewrite(Node *parsetree, const char *query_string,
/* /*
* (2) Rewrite the queries, as necessary * (2) Rewrite the queries, as necessary
*/ */
querytree_list = pg_rewrite_queries(querytree_list); querytree_list = pg_rewrite_query(query);
return querytree_list; 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 * Note: query must just have come from the parser, because we do not do
* AcquireRewriteLocks() on them. * AcquireRewriteLocks() on it.
*/ */
static List * static List *
pg_rewrite_queries(List *querytree_list) pg_rewrite_query(Query *query)
{ {
List *new_list = NIL; List *querytree_list;
ListCell *list_item;
if (log_parser_stats) if (log_parser_stats)
ResetUsage(); ResetUsage();
/* if (Debug_print_parse)
* rewritten queries are collected in new_list. Note there may be more or elog_node_display(DEBUG1, "parse tree", query,
* fewer than in the original list. Debug_pretty_print);
*/
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);
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) if (log_parser_stats)
ShowUsage("REWRITER STATISTICS"); ShowUsage("REWRITER STATISTICS");
#ifdef COPY_PARSE_PLAN_TREES #ifdef COPY_PARSE_PLAN_TREES
/* Optional debugging check: pass querytree output through copyObject() */
{
List *new_list;
/* new_list = (List *) copyObject(querytree_list);
* Optional debugging check: pass querytree output through copyObject() /* This checks both copyObject() and the equal() routines... */
*/ if (!equal(new_list, querytree_list))
new_list = (List *) copyObject(querytree_list); elog(WARNING, "copyObject() failed to produce equal parse tree");
/* This checks both copyObject() and the equal() routines... */ else
if (!equal(new_list, querytree_list)) querytree_list = new_list;
elog(WARNING, "copyObject() failed to produce an equal parse tree"); }
else
querytree_list = new_list;
#endif #endif
if (Debug_print_rewritten) if (Debug_print_rewritten)
...@@ -1139,6 +1126,7 @@ exec_parse_message(const char *query_string, /* string to execute */ ...@@ -1139,6 +1126,7 @@ exec_parse_message(const char *query_string, /* string to execute */
if (parsetree_list != NIL) if (parsetree_list != NIL)
{ {
Query *query;
int i; int i;
raw_parse_tree = (Node *) linitial(parsetree_list); raw_parse_tree = (Node *) linitial(parsetree_list);
...@@ -1175,10 +1163,10 @@ exec_parse_message(const char *query_string, /* string to execute */ ...@@ -1175,10 +1163,10 @@ exec_parse_message(const char *query_string, /* string to execute */
if (log_parser_stats) if (log_parser_stats)
ResetUsage(); ResetUsage();
querytree_list = parse_analyze_varparams(copyObject(raw_parse_tree), query = parse_analyze_varparams(copyObject(raw_parse_tree),
query_string, query_string,
&paramTypes, &paramTypes,
&numParams); &numParams);
/* /*
* Check all parameter types got determined. * Check all parameter types got determined.
...@@ -1197,7 +1185,7 @@ exec_parse_message(const char *query_string, /* string to execute */ ...@@ -1197,7 +1185,7 @@ exec_parse_message(const char *query_string, /* string to execute */
if (log_parser_stats) if (log_parser_stats)
ShowUsage("PARSE ANALYSIS STATISTICS"); 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 * If this is the unnamed statement and it has parameters, defer query
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
* *
* *
* IDENTIFICATION * 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 @@ ...@@ -45,7 +45,7 @@
#include "commands/vacuum.h" #include "commands/vacuum.h"
#include "commands/view.h" #include "commands/view.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "parser/analyze.h" #include "parser/parse_utilcmd.h"
#include "postmaster/bgwriter.h" #include "postmaster/bgwriter.h"
#include "rewrite/rewriteDefine.h" #include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteRemove.h" #include "rewrite/rewriteRemove.h"
...@@ -544,17 +544,47 @@ ProcessUtility(Node *parsetree, ...@@ -544,17 +544,47 @@ ProcessUtility(Node *parsetree,
case T_CreateStmt: case T_CreateStmt:
{ {
List *stmts;
ListCell *l;
Oid relOid; Oid relOid;
relOid = DefineRelation((CreateStmt *) parsetree, /* Run parse analysis ... */
RELKIND_RELATION); stmts = transformCreateStmt((CreateStmt *) parsetree,
queryString);
/* /* ... and do it */
* Let AlterTableCreateToastTable decide if this one needs a foreach(l, stmts)
* secondary relation too. {
*/ Node *stmt = (Node *) lfirst(l);
CommandCounterIncrement();
AlterTableCreateToastTable(relOid); 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; break;
...@@ -693,7 +723,40 @@ ProcessUtility(Node *parsetree, ...@@ -693,7 +723,40 @@ ProcessUtility(Node *parsetree,
break; break;
case T_AlterTableStmt: 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; break;
case T_AlterDomainStmt: case T_AlterDomainStmt:
...@@ -812,7 +875,7 @@ ProcessUtility(Node *parsetree, ...@@ -812,7 +875,7 @@ ProcessUtility(Node *parsetree,
CheckRelationOwnership(stmt->relation, true); CheckRelationOwnership(stmt->relation, true);
/* Run parse analysis ... */ /* Run parse analysis ... */
stmt = analyzeIndexStmt(stmt, queryString); stmt = transformIndexStmt(stmt, queryString);
/* ... and do it */ /* ... and do it */
DefineIndex(stmt->relation, /* relation */ DefineIndex(stmt->relation, /* relation */
...@@ -1605,7 +1668,7 @@ CreateCommandTag(Node *parsetree) ...@@ -1605,7 +1668,7 @@ CreateCommandTag(Node *parsetree)
/* /*
* We might be supporting ALTER INDEX here, so set the * 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 * possibilities with ALTER TABLE
*/ */
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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, ...@@ -36,6 +36,8 @@ extern TargetEntry *makeTargetEntry(Expr *expr,
extern TargetEntry *flatCopyTargetEntry(TargetEntry *src_tle); extern TargetEntry *flatCopyTargetEntry(TargetEntry *src_tle);
extern FromExpr *makeFromExpr(List *fromlist, Node *quals);
extern Const *makeConst(Oid consttype, extern Const *makeConst(Oid consttype,
int32 consttypmod, int32 consttypmod,
int constlen, int constlen,
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 @@ ...@@ -21,7 +21,7 @@
typedef enum QuerySource typedef enum QuerySource
{ {
QSRC_ORIGINAL, /* original parsetree (explicit query) */ 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_INSTEAD_RULE, /* added by unconditional INSTEAD rule */
QSRC_QUAL_INSTEAD_RULE, /* added by conditional INSTEAD rule */ QSRC_QUAL_INSTEAD_RULE, /* added by conditional INSTEAD rule */
QSRC_NON_INSTEAD_RULE /* added by non-INSTEAD rule */ QSRC_NON_INSTEAD_RULE /* added by non-INSTEAD rule */
...@@ -799,10 +799,12 @@ typedef struct SetOperationStmt ...@@ -799,10 +799,12 @@ typedef struct SetOperationStmt
/***************************************************************************** /*****************************************************************************
* Other Statements (no optimizations required) * Other Statements (no optimizations required)
* *
* Some of them require a little bit of transformation (which is also * These are not touched by parser/analyze.c except to put them into
* done by transformStmt). The whole structure is then passed on to * the utilityStmt field of a Query. This is eventually passed to
* ProcessUtility (by-passing the optimization step) as the utilityStmt * ProcessUtility (by-passing rewriting and planning). Some of the
* field in Query. * 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 ...@@ -886,7 +888,7 @@ typedef enum AlterTableType
AT_ReAddIndex, /* internal to commands/tablecmds.c */ AT_ReAddIndex, /* internal to commands/tablecmds.c */
AT_AddConstraint, /* add constraint */ AT_AddConstraint, /* add constraint */
AT_ProcessedConstraint, /* pre-processed add constraint (local in AT_ProcessedConstraint, /* pre-processed add constraint (local in
* parser/analyze.c) */ * parser/parse_utilcmd.c) */
AT_DropConstraint, /* drop constraint */ AT_DropConstraint, /* drop constraint */
AT_DropConstraintQuietly, /* drop constraint, no error/warning (local in AT_DropConstraintQuietly, /* drop constraint, no error/warning (local in
* commands/tablecmds.c) */ * commands/tablecmds.c) */
...@@ -1083,7 +1085,7 @@ typedef struct CreateStmt ...@@ -1083,7 +1085,7 @@ typedef struct CreateStmt
* relation). We should never have both in the same node! * relation). We should never have both in the same node!
* *
* Constraint attributes (DEFERRABLE etc) are initially represented as * 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 * a pass through the constraints list to attach the info to the appropriate
* FkConstraint node (and, perhaps, someday to other kinds of constraints). * FkConstraint node (and, perhaps, someday to other kinds of constraints).
* ---------- * ----------
......
/*------------------------------------------------------------------------- /*-------------------------------------------------------------------------
* *
* analyze.h * analyze.h
* parse analysis for optimizable statements
* *
* *
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 @@ ...@@ -16,16 +17,14 @@
#include "parser/parse_node.h" #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); 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); Oid **paramTypes, int *numParams);
extern List *parse_sub_analyze(Node *parseTree, ParseState *parentParseState);
extern IndexStmt *analyzeIndexStmt(IndexStmt *stmt, const char *queryString); extern Query *parse_sub_analyze(Node *parseTree, ParseState *parentParseState);
extern void analyzeRuleStmt(RuleStmt *stmt, const char *queryString, extern Query *transformStmt(ParseState *pstate, Node *parseTree);
List **actions, Node **whereClause);
extern List *analyzeCreateSchemaStmt(CreateSchemaStmt *stmt);
extern void CheckSelectLocking(Query *qry); extern void CheckSelectLocking(Query *qry);
extern void applyLockingClause(Query *qry, Index rtindex, extern void applyLockingClause(Query *qry, Index rtindex,
bool forUpdate, bool noWait); bool forUpdate, bool noWait);
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 ...@@ -83,6 +83,7 @@ typedef struct ParseState
} ParseState; } ParseState;
extern ParseState *make_parsestate(ParseState *parentParseState); extern ParseState *make_parsestate(ParseState *parentParseState);
extern void free_parsestate(ParseState *pstate);
extern int parser_errposition(ParseState *pstate, int location); extern int parser_errposition(ParseState *pstate, int location);
extern Var *make_var(ParseState *pstate, RangeTblEntry *rte, int attrno); extern Var *make_var(ParseState *pstate, RangeTblEntry *rte, int attrno);
......
/*-------------------------------------------------------------------------
*
* 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 */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment