diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
commit | 46651ce6fe013220ed397add242004d764fc0153 (patch) | |
tree | 6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/backend/parser/parse_utilcmd.c | |
parent | Initial commit. (diff) | |
download | postgresql-14-upstream.tar.xz postgresql-14-upstream.zip |
Adding upstream version 14.5.upstream/14.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/backend/parser/parse_utilcmd.c | 4382 |
1 files changed, 4382 insertions, 0 deletions
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c new file mode 100644 index 0000000..ecd5871 --- /dev/null +++ b/src/backend/parser/parse_utilcmd.c @@ -0,0 +1,4382 @@ +/*------------------------------------------------------------------------- + * + * 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. + * + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/backend/parser/parse_utilcmd.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/amapi.h" +#include "access/htup_details.h" +#include "access/relation.h" +#include "access/reloptions.h" +#include "access/table.h" +#include "access/toast_compression.h" +#include "catalog/dependency.h" +#include "catalog/heap.h" +#include "catalog/index.h" +#include "catalog/namespace.h" +#include "catalog/pg_am.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_statistic_ext.h" +#include "catalog/pg_type.h" +#include "commands/comment.h" +#include "commands/defrem.h" +#include "commands/sequence.h" +#include "commands/tablecmds.h" +#include "commands/tablespace.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/optimizer.h" +#include "parser/analyze.h" +#include "parser/parse_clause.h" +#include "parser/parse_coerce.h" +#include "parser/parse_collate.h" +#include "parser/parse_expr.h" +#include "parser/parse_relation.h" +#include "parser/parse_target.h" +#include "parser/parse_type.h" +#include "parser/parse_utilcmd.h" +#include "parser/parser.h" +#include "rewrite/rewriteManip.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/partcache.h" +#include "utils/rel.h" +#include "utils/ruleutils.h" +#include "utils/syscache.h" +#include "utils/typcache.h" + + +/* State shared by transformCreateStmt and its subroutines */ +typedef struct +{ + ParseState *pstate; /* overall parser state */ + const char *stmtType; /* "CREATE [FOREIGN] TABLE" or "ALTER TABLE" */ + RangeVar *relation; /* relation to create */ + Relation rel; /* opened/locked rel, if ALTER */ + List *inhRelations; /* relations to inherit from */ + bool isforeign; /* true if CREATE/ALTER FOREIGN TABLE */ + 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 *likeclauses; /* LIKE clauses that need post-processing */ + List *extstats; /* cloned extended statistics */ + 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 */ + bool ispartitioned; /* true if table is partitioned */ + PartitionBoundSpec *partbound; /* transformed FOR VALUES */ + bool ofType; /* true if statement contains OF typename */ +} CreateStmtContext; + +/* State shared by transformCreateSchemaStmt and its subroutines */ +typedef struct +{ + const char *stmtType; /* "CREATE SCHEMA" or "ALTER SCHEMA" */ + char *schemaname; /* name of schema */ + RoleSpec *authrole; /* 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(CreateStmtContext *cxt, + ColumnDef *column); +static void transformTableConstraint(CreateStmtContext *cxt, + Constraint *constraint); +static void transformTableLikeClause(CreateStmtContext *cxt, + TableLikeClause *table_like_clause); +static void transformOfType(CreateStmtContext *cxt, + TypeName *ofTypename); +static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel, + Oid heapRelid, Oid source_statsid); +static List *get_collation(Oid collation, Oid actual_datatype); +static List *get_opclass(Oid opclass, Oid actual_datatype); +static void transformIndexConstraints(CreateStmtContext *cxt); +static IndexStmt *transformIndexConstraint(Constraint *constraint, + CreateStmtContext *cxt); +static void transformExtendedStatistics(CreateStmtContext *cxt); +static void transformFKConstraints(CreateStmtContext *cxt, + bool skipValidation, + bool isAddConstraint); +static void transformCheckConstraints(CreateStmtContext *cxt, + bool skipValidation); +static void transformConstraintAttrs(CreateStmtContext *cxt, + List *constraintList); +static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column); +static void setSchemaName(char *context_schema, char **stmt_schema_name); +static void transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd); +static List *transformPartitionRangeBounds(ParseState *pstate, List *blist, + Relation parent); +static void validateInfiniteBounds(ParseState *pstate, List *blist); +static Const *transformPartitionBoundValue(ParseState *pstate, Node *con, + const char *colName, Oid colType, int32 colTypmod, + Oid partCollation); + + +/* + * 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. + * In addition to normal utility commands such as AlterTableStmt and + * IndexStmt, the result list may contain TableLikeClause(s), representing + * the need to perform additional parse analysis after DefineRelation(). + * + * SQL 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; + Oid namespaceid; + Oid existing_relid; + ParseCallbackState pcbstate; + + /* Set up pstate */ + pstate = make_parsestate(NULL); + pstate->p_sourcetext = queryString; + + /* + * Look up the creation namespace. This also checks permissions on the + * target namespace, locks it against concurrent drops, checks for a + * preexisting relation in that namespace with the same name, and updates + * stmt->relation->relpersistence if the selected namespace is temporary. + */ + setup_parser_errposition_callback(&pcbstate, pstate, + stmt->relation->location); + namespaceid = + RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock, + &existing_relid); + cancel_parser_errposition_callback(&pcbstate); + + /* + * If the relation already exists and the user specified "IF NOT EXISTS", + * bail out with a NOTICE. + */ + if (stmt->if_not_exists && OidIsValid(existing_relid)) + { + /* + * If we are in an extension script, insist that the pre-existing + * object be a member of the extension, to avoid security risks. + */ + ObjectAddress address; + + ObjectAddressSet(address, RelationRelationId, existing_relid); + checkMembershipInCurrentExtension(&address); + + /* OK to skip */ + ereport(NOTICE, + (errcode(ERRCODE_DUPLICATE_TABLE), + errmsg("relation \"%s\" already exists, skipping", + stmt->relation->relname))); + return NIL; + } + + /* + * If the target relation name isn't schema-qualified, make it so. This + * prevents some corner cases in which added-on rewritten commands might + * think they should apply to other relations that have the same name and + * are earlier in the search path. But a local temp table is effectively + * specified to be in pg_temp, so no need for anything extra in that case. + */ + if (stmt->relation->schemaname == NULL + && stmt->relation->relpersistence != RELPERSISTENCE_TEMP) + stmt->relation->schemaname = get_namespace_name(namespaceid); + + /* Set up CreateStmtContext */ + cxt.pstate = pstate; + if (IsA(stmt, CreateForeignTableStmt)) + { + cxt.stmtType = "CREATE FOREIGN TABLE"; + cxt.isforeign = true; + } + else + { + cxt.stmtType = "CREATE TABLE"; + cxt.isforeign = false; + } + 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.likeclauses = NIL; + cxt.extstats = NIL; + cxt.blist = NIL; + cxt.alist = NIL; + cxt.pkey = NULL; + cxt.ispartitioned = stmt->partspec != NULL; + cxt.partbound = stmt->partbound; + cxt.ofType = (stmt->ofTypename != NULL); + + Assert(!stmt->ofTypename || !stmt->inhRelations); /* grammar enforces */ + + if (stmt->ofTypename) + transformOfType(&cxt, stmt->ofTypename); + + if (stmt->partspec) + { + if (stmt->inhRelations && !stmt->partbound) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot create partitioned table as inheritance child"))); + } + + /* + * 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(&cxt, (ColumnDef *) element); + break; + + case T_Constraint: + transformTableConstraint(&cxt, (Constraint *) element); + break; + + case T_TableLikeClause: + transformTableLikeClause(&cxt, (TableLikeClause *) element); + break; + + default: + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(element)); + break; + } + } + + /* + * Transfer anything we already have in cxt.alist into save_alist, to keep + * it separate from the output of transformIndexConstraints. (This may + * not be necessary anymore, but we'll keep doing it to preserve the + * historical order of execution of the alist commands.) + */ + save_alist = cxt.alist; + cxt.alist = NIL; + + Assert(stmt->constraints == NIL); + + /* + * Postprocess constraints that give rise to index definitions. + */ + transformIndexConstraints(&cxt); + + /* + * Re-consideration of LIKE clauses should happen after creation of + * indexes, but before creation of foreign keys. This order is critical + * because a LIKE clause may attempt to create a primary key. If there's + * also a pkey in the main CREATE TABLE list, creation of that will not + * check for a duplicate at runtime (since index_check_primary_key() + * expects that we rejected dups here). Creation of the LIKE-generated + * pkey behaves like ALTER TABLE ADD, so it will check, but obviously that + * only works if it happens second. On the other hand, we want to make + * pkeys before foreign key constraints, in case the user tries to make a + * self-referential FK. + */ + cxt.alist = list_concat(cxt.alist, cxt.likeclauses); + + /* + * Postprocess foreign-key constraints. + */ + transformFKConstraints(&cxt, true, false); + + /* + * Postprocess check constraints. + * + * For regular tables all constraints can be marked valid immediately, + * because the table is new therefore empty. Not so for foreign tables. + */ + transformCheckConstraints(&cxt, !cxt.isforeign); + + /* + * Postprocess extended statistics. + */ + transformExtendedStatistics(&cxt); + + /* + * 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; +} + +/* + * generateSerialExtraStmts + * Generate CREATE SEQUENCE and ALTER SEQUENCE ... OWNED BY statements + * to create the sequence for a serial or identity column. + * + * This includes determining the name the sequence will have. The caller + * can ask to get back the name components by passing non-null pointers + * for snamespace_p and sname_p. + */ +static void +generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, + Oid seqtypid, List *seqoptions, + bool for_identity, bool col_exists, + char **snamespace_p, char **sname_p) +{ + ListCell *option; + DefElem *nameEl = NULL; + Oid snamespaceid; + char *snamespace; + char *sname; + CreateSeqStmt *seqstmt; + AlterSeqStmt *altseqstmt; + List *attnamelist; + int nameEl_idx = -1; + + /* + * Determine namespace and name to use for the sequence. + * + * First, check if a sequence name was passed in as an option. This is + * used by pg_dump. Else, generate a name. + * + * 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. + */ + foreach(option, seqoptions) + { + DefElem *defel = lfirst_node(DefElem, option); + + if (strcmp(defel->defname, "sequence_name") == 0) + { + if (nameEl) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + nameEl = defel; + nameEl_idx = foreach_current_index(option); + } + } + + if (nameEl) + { + RangeVar *rv = makeRangeVarFromNameList(castNode(List, nameEl->arg)); + + snamespace = rv->schemaname; + if (!snamespace) + { + /* Given unqualified SEQUENCE NAME, select namespace */ + if (cxt->rel) + snamespaceid = RelationGetNamespace(cxt->rel); + else + snamespaceid = RangeVarGetCreationNamespace(cxt->relation); + snamespace = get_namespace_name(snamespaceid); + } + sname = rv->relname; + /* Remove the SEQUENCE NAME item from seqoptions */ + seqoptions = list_delete_nth_cell(seqoptions, nameEl_idx); + } + else + { + if (cxt->rel) + snamespaceid = RelationGetNamespace(cxt->rel); + else + { + snamespaceid = RangeVarGetCreationNamespace(cxt->relation); + RangeVarAdjustRelationPersistence(cxt->relation, snamespaceid); + } + snamespace = get_namespace_name(snamespaceid); + sname = ChooseRelationName(cxt->relation->relname, + column->colname, + "seq", + snamespaceid, + false); + } + + ereport(DEBUG1, + (errmsg_internal("%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->for_identity = for_identity; + seqstmt->sequence = makeRangeVar(snamespace, sname, -1); + seqstmt->options = seqoptions; + + /* + * If a sequence data type was specified, add it to the options. Prepend + * to the list rather than append; in case a user supplied their own AS + * clause, the "redundant options" error will point to their occurrence, + * not our synthetic one. + */ + if (seqtypid) + seqstmt->options = lcons(makeDefElem("as", + (Node *) makeTypeNameFromOid(seqtypid, -1), + -1), + seqstmt->options); + + /* + * If this is ALTER ADD COLUMN, make sure the sequence will be owned by + * the table's owner. The current user might be someone else (perhaps a + * superuser, or someone who's only a member of the owning role), but the + * SEQUENCE OWNED BY mechanisms will bleat unless table and sequence have + * exactly the same owning role. + */ + if (cxt->rel) + seqstmt->ownerId = cxt->rel->rd_rel->relowner; + else + seqstmt->ownerId = InvalidOid; + + cxt->blist = lappend(cxt->blist, seqstmt); + + /* + * Store the identity sequence name that we decided on. ALTER TABLE ... + * ADD COLUMN ... IDENTITY needs this so that it can fill the new column + * with values from the sequence, while the association of the sequence + * with the table is not set until after the ALTER TABLE. + */ + column->identitySequence = seqstmt->sequence; + + /* + * Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence as + * owned by this column, and add it to the appropriate list of things to + * be done along with this CREATE/ALTER TABLE. In a CREATE or ALTER ADD + * COLUMN, it must be done after the statement because we don't know the + * column's attnum yet. But if we do have the attnum (in AT_AddIdentity), + * we can do the marking immediately, which improves some ALTER TABLE + * behaviors. + */ + altseqstmt = makeNode(AlterSeqStmt); + altseqstmt->sequence = makeRangeVar(snamespace, sname, -1); + attnamelist = list_make3(makeString(snamespace), + makeString(cxt->relation->relname), + makeString(column->colname)); + altseqstmt->options = list_make1(makeDefElem("owned_by", + (Node *) attnamelist, -1)); + altseqstmt->for_identity = for_identity; + + if (col_exists) + cxt->blist = lappend(cxt->blist, altseqstmt); + else + cxt->alist = lappend(cxt->alist, altseqstmt); + + if (snamespace_p) + *snamespace_p = snamespace; + if (sname_p) + *sname_p = sname; +} + +/* + * transformColumnDefinition - + * transform a single ColumnDef within CREATE TABLE + * Also used in ALTER TABLE ADD COLUMN + */ +static void +transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) +{ + bool is_serial; + bool saw_nullable; + bool saw_default; + bool saw_identity; + bool saw_generated; + ListCell *clist; + + cxt->columns = lappend(cxt->columns, column); + + /* Check for SERIAL pseudo-types */ + is_serial = false; + if (column->typeName + && list_length(column->typeName->names) == 1 + && !column->typeName->pct_type) + { + char *typname = strVal(linitial(column->typeName->names)); + + if (strcmp(typname, "smallserial") == 0 || + strcmp(typname, "serial2") == 0) + { + is_serial = true; + column->typeName->names = NIL; + column->typeName->typeOid = INT2OID; + } + else if (strcmp(typname, "serial") == 0 || + strcmp(typname, "serial4") == 0) + { + is_serial = true; + column->typeName->names = NIL; + column->typeName->typeOid = INT4OID; + } + else if (strcmp(typname, "bigserial") == 0 || + strcmp(typname, "serial8") == 0) + { + is_serial = true; + column->typeName->names = NIL; + column->typeName->typeOid = INT8OID; + } + + /* + * We have to reject "serial[]" explicitly, because once we've set + * typeid, LookupTypeName won't notice arrayBounds. We don't need any + * special coding for serial(typmod) though. + */ + if (is_serial && column->typeName->arrayBounds != NIL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("array of serial is not implemented"), + parser_errposition(cxt->pstate, + column->typeName->location))); + } + + /* Do necessary work on the column type declaration */ + if (column->typeName) + transformColumnType(cxt, column); + + /* Special actions for SERIAL pseudo-types */ + if (is_serial) + { + char *snamespace; + char *sname; + char *qstring; + A_Const *snamenode; + TypeCast *castnode; + FuncCall *funccallnode; + Constraint *constraint; + + generateSerialExtraStmts(cxt, column, + column->typeName->typeOid, NIL, + false, false, + &snamespace, &sname); + + /* + * Create appropriate constraints for SERIAL. We do this in full, + * rather than shortcutting, so that we will detect any conflicting + * constraints the user wrote (like a different DEFAULT). + * + * Create an expression tree representing the function call + * nextval('sequencename'). We cannot reduce the raw tree to cooked + * form until after the sequence is created, but there's no need to do + * so. + */ + qstring = quote_qualified_identifier(snamespace, sname); + snamenode = makeNode(A_Const); + snamenode->val.type = T_String; + snamenode->val.val.str = qstring; + snamenode->location = -1; + castnode = makeNode(TypeCast); + castnode->typeName = SystemTypeName("regclass"); + castnode->arg = (Node *) snamenode; + castnode->location = -1; + funccallnode = makeFuncCall(SystemFuncName("nextval"), + list_make1(castnode), + COERCE_EXPLICIT_CALL, + -1); + constraint = makeNode(Constraint); + constraint->contype = CONSTR_DEFAULT; + constraint->location = -1; + constraint->raw_expr = (Node *) funccallnode; + constraint->cooked_expr = NULL; + column->constraints = lappend(column->constraints, constraint); + + constraint = makeNode(Constraint); + constraint->contype = CONSTR_NOTNULL; + constraint->location = -1; + column->constraints = lappend(column->constraints, constraint); + } + + /* Process column constraints, if any... */ + transformConstraintAttrs(cxt, column->constraints); + + saw_nullable = false; + saw_default = false; + saw_identity = false; + saw_generated = false; + + foreach(clist, column->constraints) + { + Constraint *constraint = lfirst_node(Constraint, clist); + + 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), + parser_errposition(cxt->pstate, + constraint->location))); + 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), + parser_errposition(cxt->pstate, + constraint->location))); + 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), + parser_errposition(cxt->pstate, + constraint->location))); + column->raw_default = constraint->raw_expr; + Assert(constraint->cooked_expr == NULL); + saw_default = true; + break; + + case CONSTR_IDENTITY: + { + Type ctype; + Oid typeOid; + + if (cxt->ofType) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("identity columns are not supported on typed tables"))); + if (cxt->partbound) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("identity columns are not supported on partitions"))); + + ctype = typenameType(cxt->pstate, column->typeName, NULL); + typeOid = ((Form_pg_type) GETSTRUCT(ctype))->oid; + ReleaseSysCache(ctype); + + if (saw_identity) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("multiple identity specifications for column \"%s\" of table \"%s\"", + column->colname, cxt->relation->relname), + parser_errposition(cxt->pstate, + constraint->location))); + + generateSerialExtraStmts(cxt, column, + typeOid, constraint->options, + true, false, + NULL, NULL); + + column->identity = constraint->generated_when; + saw_identity = true; + + /* An identity column is implicitly NOT 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), + parser_errposition(cxt->pstate, + constraint->location))); + column->is_not_null = true; + saw_nullable = true; + break; + } + + case CONSTR_GENERATED: + if (cxt->ofType) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("generated columns are not supported on typed tables"))); + if (cxt->partbound) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("generated columns are not supported on partitions"))); + + if (saw_generated) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("multiple generation clauses specified for column \"%s\" of table \"%s\"", + column->colname, cxt->relation->relname), + parser_errposition(cxt->pstate, + constraint->location))); + column->generated = ATTRIBUTE_GENERATED_STORED; + column->raw_default = constraint->raw_expr; + Assert(constraint->cooked_expr == NULL); + saw_generated = true; + break; + + case CONSTR_CHECK: + cxt->ckconstraints = lappend(cxt->ckconstraints, constraint); + break; + + case CONSTR_PRIMARY: + if (cxt->isforeign) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("primary key constraints are not supported on foreign tables"), + parser_errposition(cxt->pstate, + constraint->location))); + /* FALL THRU */ + + case CONSTR_UNIQUE: + if (cxt->isforeign) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unique constraints are not supported on foreign tables"), + parser_errposition(cxt->pstate, + constraint->location))); + if (constraint->keys == NIL) + constraint->keys = list_make1(makeString(column->colname)); + cxt->ixconstraints = lappend(cxt->ixconstraints, constraint); + break; + + case CONSTR_EXCLUSION: + /* grammar does not allow EXCLUDE as a column constraint */ + elog(ERROR, "column exclusion constraints are not supported"); + break; + + case CONSTR_FOREIGN: + if (cxt->isforeign) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("foreign key constraints are not supported on foreign tables"), + parser_errposition(cxt->pstate, + constraint->location))); + + /* + * Fill in the current attribute's name and throw it into the + * list of FK constraints to be processed later. + */ + constraint->fk_attrs = list_make1(makeString(column->colname)); + cxt->fkconstraints = lappend(cxt->fkconstraints, 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; + } + + if (saw_default && saw_identity) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("both default and identity specified for column \"%s\" of table \"%s\"", + column->colname, cxt->relation->relname), + parser_errposition(cxt->pstate, + constraint->location))); + + if (saw_default && saw_generated) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("both default and generation expression specified for column \"%s\" of table \"%s\"", + column->colname, cxt->relation->relname), + parser_errposition(cxt->pstate, + constraint->location))); + + if (saw_identity && saw_generated) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("both identity and generation expression specified for column \"%s\" of table \"%s\"", + column->colname, cxt->relation->relname), + parser_errposition(cxt->pstate, + constraint->location))); + } + + /* + * If needed, generate ALTER FOREIGN TABLE ALTER COLUMN statement to add + * per-column foreign data wrapper options to this column after creation. + */ + if (column->fdwoptions != NIL) + { + AlterTableStmt *stmt; + AlterTableCmd *cmd; + + cmd = makeNode(AlterTableCmd); + cmd->subtype = AT_AlterColumnGenericOptions; + cmd->name = column->colname; + cmd->def = (Node *) column->fdwoptions; + cmd->behavior = DROP_RESTRICT; + cmd->missing_ok = false; + + stmt = makeNode(AlterTableStmt); + stmt->relation = cxt->relation; + stmt->cmds = NIL; + stmt->objtype = OBJECT_FOREIGN_TABLE; + stmt->cmds = lappend(stmt->cmds, cmd); + + cxt->alist = lappend(cxt->alist, stmt); + } +} + +/* + * transformTableConstraint + * transform a Constraint node within CREATE TABLE or ALTER TABLE + */ +static void +transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint) +{ + switch (constraint->contype) + { + case CONSTR_PRIMARY: + if (cxt->isforeign) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("primary key constraints are not supported on foreign tables"), + parser_errposition(cxt->pstate, + constraint->location))); + cxt->ixconstraints = lappend(cxt->ixconstraints, constraint); + break; + + case CONSTR_UNIQUE: + if (cxt->isforeign) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unique constraints are not supported on foreign tables"), + parser_errposition(cxt->pstate, + constraint->location))); + cxt->ixconstraints = lappend(cxt->ixconstraints, constraint); + break; + + case CONSTR_EXCLUSION: + if (cxt->isforeign) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("exclusion constraints are not supported on foreign tables"), + parser_errposition(cxt->pstate, + constraint->location))); + if (cxt->ispartitioned) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("exclusion constraints are not supported on partitioned tables"), + parser_errposition(cxt->pstate, + constraint->location))); + cxt->ixconstraints = lappend(cxt->ixconstraints, constraint); + break; + + case CONSTR_CHECK: + cxt->ckconstraints = lappend(cxt->ckconstraints, constraint); + break; + + case CONSTR_FOREIGN: + if (cxt->isforeign) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("foreign key constraints are not supported on foreign tables"), + parser_errposition(cxt->pstate, + constraint->location))); + cxt->fkconstraints = lappend(cxt->fkconstraints, 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; + } +} + +/* + * transformTableLikeClause + * + * Change the LIKE <srctable> portion of a CREATE TABLE statement into + * column definitions that recreate the user defined column portions of + * <srctable>. Also, if there are any LIKE options that we can't fully + * process at this point, add the TableLikeClause to cxt->likeclauses, which + * will cause utility.c to call expandTableLikeClause() after the new + * table has been created. + */ +static void +transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_clause) +{ + AttrNumber parent_attno; + Relation relation; + TupleDesc tupleDesc; + AclResult aclresult; + char *comment; + ParseCallbackState pcbstate; + + setup_parser_errposition_callback(&pcbstate, cxt->pstate, + table_like_clause->relation->location); + + /* we could support LIKE in many cases, but worry about it another day */ + if (cxt->isforeign) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("LIKE is not supported for creating foreign tables"))); + + /* Open the relation referenced by the LIKE clause */ + relation = relation_openrv(table_like_clause->relation, AccessShareLock); + + if (relation->rd_rel->relkind != RELKIND_RELATION && + relation->rd_rel->relkind != RELKIND_VIEW && + relation->rd_rel->relkind != RELKIND_MATVIEW && + relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE && + relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE && + relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table", + RelationGetRelationName(relation)))); + + cancel_parser_errposition_callback(&pcbstate); + + /* + * Check for privileges + */ + if (relation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) + { + aclresult = pg_type_aclcheck(relation->rd_rel->reltype, GetUserId(), + ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_TYPE, + RelationGetRelationName(relation)); + } + else + { + aclresult = pg_class_aclcheck(RelationGetRelid(relation), GetUserId(), + ACL_SELECT); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, get_relkind_objtype(relation->rd_rel->relkind), + RelationGetRelationName(relation)); + } + + tupleDesc = RelationGetDescr(relation); + + /* + * Insert the copied attributes into the cxt for the new table definition. + * We must do this now so that they appear in the table in the relative + * position where the LIKE clause is, as required by SQL99. + */ + for (parent_attno = 1; parent_attno <= tupleDesc->natts; + parent_attno++) + { + Form_pg_attribute attribute = TupleDescAttr(tupleDesc, + 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->is_from_type = false; + def->storage = 0; + def->raw_default = NULL; + def->cooked_default = NULL; + def->collClause = NULL; + def->collOid = attribute->attcollation; + def->constraints = NIL; + def->location = -1; + + /* + * Add to column list + */ + cxt->columns = lappend(cxt->columns, def); + + /* + * Although we don't transfer the column's default/generation + * expression now, we need to mark it GENERATED if appropriate. + */ + if (attribute->atthasdef && attribute->attgenerated && + (table_like_clause->options & CREATE_TABLE_LIKE_GENERATED)) + def->generated = attribute->attgenerated; + + /* + * Copy identity if requested + */ + if (attribute->attidentity && + (table_like_clause->options & CREATE_TABLE_LIKE_IDENTITY)) + { + Oid seq_relid; + List *seq_options; + + /* + * find sequence owned by old column; extract sequence parameters; + * build new create sequence command + */ + seq_relid = getIdentitySequence(RelationGetRelid(relation), attribute->attnum, false); + seq_options = sequence_options(seq_relid); + generateSerialExtraStmts(cxt, def, + InvalidOid, seq_options, + true, false, + NULL, NULL); + def->identity = attribute->attidentity; + } + + /* Likewise, copy storage if requested */ + if (table_like_clause->options & CREATE_TABLE_LIKE_STORAGE) + def->storage = attribute->attstorage; + else + def->storage = 0; + + /* Likewise, copy compression if requested */ + if ((table_like_clause->options & CREATE_TABLE_LIKE_COMPRESSION) != 0 + && CompressionMethodIsValid(attribute->attcompression)) + def->compression = + pstrdup(GetCompressionMethodName(attribute->attcompression)); + else + def->compression = NULL; + + /* Likewise, copy comment if requested */ + if ((table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS) && + (comment = GetComment(attribute->attrelid, + RelationRelationId, + attribute->attnum)) != NULL) + { + CommentStmt *stmt = makeNode(CommentStmt); + + stmt->objtype = OBJECT_COLUMN; + stmt->object = (Node *) list_make3(makeString(cxt->relation->schemaname), + makeString(cxt->relation->relname), + makeString(def->colname)); + stmt->comment = comment; + + cxt->alist = lappend(cxt->alist, stmt); + } + } + + /* + * We cannot yet deal with defaults, CHECK constraints, or indexes, since + * we don't yet know what column numbers the copied columns will have in + * the finished table. If any of those options are specified, add the + * LIKE clause to cxt->likeclauses so that expandTableLikeClause will be + * called after we do know that. Also, remember the relation OID so that + * expandTableLikeClause is certain to open the same table. + */ + if (table_like_clause->options & + (CREATE_TABLE_LIKE_DEFAULTS | + CREATE_TABLE_LIKE_GENERATED | + CREATE_TABLE_LIKE_CONSTRAINTS | + CREATE_TABLE_LIKE_INDEXES)) + { + table_like_clause->relationOid = RelationGetRelid(relation); + cxt->likeclauses = lappend(cxt->likeclauses, table_like_clause); + } + + /* + * We may copy extended statistics if requested, since the representation + * of CreateStatsStmt doesn't depend on column numbers. + */ + if (table_like_clause->options & CREATE_TABLE_LIKE_STATISTICS) + { + List *parent_extstats; + ListCell *l; + + parent_extstats = RelationGetStatExtList(relation); + + foreach(l, parent_extstats) + { + Oid parent_stat_oid = lfirst_oid(l); + CreateStatsStmt *stats_stmt; + + stats_stmt = generateClonedExtStatsStmt(cxt->relation, + RelationGetRelid(relation), + parent_stat_oid); + + /* Copy comment on statistics object, if requested */ + if (table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS) + { + comment = GetComment(parent_stat_oid, StatisticExtRelationId, 0); + + /* + * We make use of CreateStatsStmt's stxcomment option, so as + * not to need to know now what name the statistics will have. + */ + stats_stmt->stxcomment = comment; + } + + cxt->extstats = lappend(cxt->extstats, stats_stmt); + } + + list_free(parent_extstats); + } + + /* + * 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 we can run expandTableLikeClause. + */ + table_close(relation, NoLock); +} + +/* + * expandTableLikeClause + * + * Process LIKE options that require knowing the final column numbers + * assigned to the new table's columns. This executes after we have + * run DefineRelation for the new table. It returns a list of utility + * commands that should be run to generate indexes etc. + */ +List * +expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause) +{ + List *result = NIL; + List *atsubcmds = NIL; + AttrNumber parent_attno; + Relation relation; + Relation childrel; + TupleDesc tupleDesc; + TupleConstr *constr; + AttrMap *attmap; + char *comment; + + /* + * Open the relation referenced by the LIKE clause. We should still have + * the table lock obtained by transformTableLikeClause (and this'll throw + * an assertion failure if not). Hence, no need to recheck privileges + * etc. We must open the rel by OID not name, to be sure we get the same + * table. + */ + if (!OidIsValid(table_like_clause->relationOid)) + elog(ERROR, "expandTableLikeClause called on untransformed LIKE clause"); + + relation = relation_open(table_like_clause->relationOid, NoLock); + + tupleDesc = RelationGetDescr(relation); + constr = tupleDesc->constr; + + /* + * Open the newly-created child relation; we have lock on that too. + */ + childrel = relation_openrv(heapRel, NoLock); + + /* + * Construct a map from the LIKE relation's attnos to the child rel's. + * This re-checks type match etc, although it shouldn't be possible to + * have a failure since both tables are locked. + */ + attmap = build_attrmap_by_name(RelationGetDescr(childrel), + tupleDesc); + + /* + * Process defaults, if required. + */ + if ((table_like_clause->options & + (CREATE_TABLE_LIKE_DEFAULTS | CREATE_TABLE_LIKE_GENERATED)) && + constr != NULL) + { + for (parent_attno = 1; parent_attno <= tupleDesc->natts; + parent_attno++) + { + Form_pg_attribute attribute = TupleDescAttr(tupleDesc, + parent_attno - 1); + + /* + * Ignore dropped columns in the parent. + */ + if (attribute->attisdropped) + continue; + + /* + * Copy default, if present and it should be copied. We have + * separate options for plain default expressions and GENERATED + * defaults. + */ + if (attribute->atthasdef && + (attribute->attgenerated ? + (table_like_clause->options & CREATE_TABLE_LIKE_GENERATED) : + (table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS))) + { + Node *this_default = NULL; + AttrDefault *attrdef = constr->defval; + AlterTableCmd *atsubcmd; + bool found_whole_row; + + /* Find default in constraint structure */ + for (int i = 0; i < constr->num_defval; i++) + { + if (attrdef[i].adnum == parent_attno) + { + this_default = stringToNode(attrdef[i].adbin); + break; + } + } + if (this_default == NULL) + elog(ERROR, "default expression not found for attribute %d of relation \"%s\"", + parent_attno, RelationGetRelationName(relation)); + + atsubcmd = makeNode(AlterTableCmd); + atsubcmd->subtype = AT_CookedColumnDefault; + atsubcmd->num = attmap->attnums[parent_attno - 1]; + atsubcmd->def = map_variable_attnos(this_default, + 1, 0, + attmap, + InvalidOid, + &found_whole_row); + + /* + * Prevent this for the same reason as for constraints below. + * Note that defaults cannot contain any vars, so it's OK that + * the error message refers to generated columns. + */ + if (found_whole_row) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert whole-row table reference"), + errdetail("Generation expression for column \"%s\" contains a whole-row reference to table \"%s\".", + NameStr(attribute->attname), + RelationGetRelationName(relation)))); + + atsubcmds = lappend(atsubcmds, atsubcmd); + } + } + } + + /* + * Copy CHECK constraints if requested, being careful to adjust attribute + * numbers so they match the child. + */ + if ((table_like_clause->options & CREATE_TABLE_LIKE_CONSTRAINTS) && + constr != NULL) + { + int ccnum; + + for (ccnum = 0; ccnum < constr->num_check; ccnum++) + { + char *ccname = constr->check[ccnum].ccname; + char *ccbin = constr->check[ccnum].ccbin; + bool ccnoinherit = constr->check[ccnum].ccnoinherit; + Node *ccbin_node; + bool found_whole_row; + Constraint *n; + AlterTableCmd *atsubcmd; + + ccbin_node = map_variable_attnos(stringToNode(ccbin), + 1, 0, + attmap, + InvalidOid, &found_whole_row); + + /* + * We reject whole-row variables because the whole point of LIKE + * is that the new table's rowtype might later diverge from the + * parent's. So, while translation might be possible right now, + * it wouldn't be possible to guarantee it would work in future. + */ + if (found_whole_row) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert whole-row table reference"), + errdetail("Constraint \"%s\" contains a whole-row reference to table \"%s\".", + ccname, + RelationGetRelationName(relation)))); + + n = makeNode(Constraint); + n->contype = CONSTR_CHECK; + n->conname = pstrdup(ccname); + n->location = -1; + n->is_no_inherit = ccnoinherit; + n->raw_expr = NULL; + n->cooked_expr = nodeToString(ccbin_node); + + /* We can skip validation, since the new table should be empty. */ + n->skip_validation = true; + n->initially_valid = true; + + atsubcmd = makeNode(AlterTableCmd); + atsubcmd->subtype = AT_AddConstraint; + atsubcmd->def = (Node *) n; + atsubcmds = lappend(atsubcmds, atsubcmd); + + /* Copy comment on constraint */ + if ((table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS) && + (comment = GetComment(get_relation_constraint_oid(RelationGetRelid(relation), + n->conname, false), + ConstraintRelationId, + 0)) != NULL) + { + CommentStmt *stmt = makeNode(CommentStmt); + + stmt->objtype = OBJECT_TABCONSTRAINT; + stmt->object = (Node *) list_make3(makeString(heapRel->schemaname), + makeString(heapRel->relname), + makeString(n->conname)); + stmt->comment = comment; + + result = lappend(result, stmt); + } + } + } + + /* + * If we generated any ALTER TABLE actions above, wrap them into a single + * ALTER TABLE command. Stick it at the front of the result, so it runs + * before any CommentStmts we made above. + */ + if (atsubcmds) + { + AlterTableStmt *atcmd = makeNode(AlterTableStmt); + + atcmd->relation = copyObject(heapRel); + atcmd->cmds = atsubcmds; + atcmd->objtype = OBJECT_TABLE; + atcmd->missing_ok = false; + result = lcons(atcmd, result); + } + + /* + * Process indexes if required. + */ + if ((table_like_clause->options & CREATE_TABLE_LIKE_INDEXES) && + relation->rd_rel->relhasindex) + { + List *parent_indexes; + ListCell *l; + + parent_indexes = RelationGetIndexList(relation); + + foreach(l, parent_indexes) + { + Oid parent_index_oid = lfirst_oid(l); + Relation parent_index; + IndexStmt *index_stmt; + + parent_index = index_open(parent_index_oid, AccessShareLock); + + /* Build CREATE INDEX statement to recreate the parent_index */ + index_stmt = generateClonedIndexStmt(heapRel, + parent_index, + attmap, + NULL); + + /* Copy comment on index, if requested */ + if (table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS) + { + comment = GetComment(parent_index_oid, RelationRelationId, 0); + + /* + * We make use of IndexStmt's idxcomment option, so as not to + * need to know now what name the index will have. + */ + index_stmt->idxcomment = comment; + } + + result = lappend(result, index_stmt); + + index_close(parent_index, AccessShareLock); + } + } + + /* Done with child rel */ + table_close(childrel, NoLock); + + /* + * 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. + */ + table_close(relation, NoLock); + + return result; +} + +static void +transformOfType(CreateStmtContext *cxt, TypeName *ofTypename) +{ + HeapTuple tuple; + TupleDesc tupdesc; + int i; + Oid ofTypeId; + + AssertArg(ofTypename); + + tuple = typenameType(NULL, ofTypename, NULL); + check_of_type(tuple); + ofTypeId = ((Form_pg_type) GETSTRUCT(tuple))->oid; + ofTypename->typeOid = ofTypeId; /* cached for later */ + + tupdesc = lookup_rowtype_tupdesc(ofTypeId, -1); + for (i = 0; i < tupdesc->natts; i++) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, i); + ColumnDef *n; + + if (attr->attisdropped) + continue; + + n = makeNode(ColumnDef); + n->colname = pstrdup(NameStr(attr->attname)); + n->typeName = makeTypeNameFromOid(attr->atttypid, attr->atttypmod); + n->inhcount = 0; + n->is_local = true; + n->is_not_null = false; + n->is_from_type = true; + n->storage = 0; + n->raw_default = NULL; + n->cooked_default = NULL; + n->collClause = NULL; + n->collOid = attr->attcollation; + n->constraints = NIL; + n->location = -1; + cxt->columns = lappend(cxt->columns, n); + } + DecrTupleDescRefCount(tupdesc); + + ReleaseSysCache(tuple); +} + +/* + * Generate an IndexStmt node using information from an already existing index + * "source_idx". + * + * heapRel is stored into the IndexStmt's relation field, but we don't use it + * otherwise; some callers pass NULL, if they don't need it to be valid. + * (The target relation might not exist yet, so we mustn't try to access it.) + * + * Attribute numbers in expression Vars are adjusted according to attmap. + * + * If constraintOid isn't NULL, we store the OID of any constraint associated + * with the index there. + * + * Unlike transformIndexConstraint, we don't make any effort to force primary + * key columns to be NOT NULL. The larger cloning process this is part of + * should have cloned their NOT NULL status separately (and DefineIndex will + * complain if that fails to happen). + */ +IndexStmt * +generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, + const AttrMap *attmap, + Oid *constraintOid) +{ + Oid source_relid = RelationGetRelid(source_idx); + HeapTuple ht_idxrel; + HeapTuple ht_idx; + HeapTuple ht_am; + Form_pg_class idxrelrec; + Form_pg_index idxrec; + Form_pg_am amrec; + oidvector *indcollation; + oidvector *indclass; + IndexStmt *index; + List *indexprs; + ListCell *indexpr_item; + Oid indrelid; + int keyno; + Oid keycoltype; + Datum datum; + bool isnull; + + if (constraintOid) + *constraintOid = InvalidOid; + + /* + * Fetch pg_class tuple of source index. We can't use the copy in the + * relcache entry because it doesn't include optional fields. + */ + ht_idxrel = SearchSysCache1(RELOID, ObjectIdGetDatum(source_relid)); + if (!HeapTupleIsValid(ht_idxrel)) + elog(ERROR, "cache lookup failed for relation %u", source_relid); + idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel); + + /* Fetch pg_index tuple for source index from relcache entry */ + ht_idx = source_idx->rd_indextuple; + idxrec = (Form_pg_index) GETSTRUCT(ht_idx); + indrelid = idxrec->indrelid; + + /* Fetch the pg_am tuple of the index' access method */ + ht_am = SearchSysCache1(AMOID, ObjectIdGetDatum(idxrelrec->relam)); + if (!HeapTupleIsValid(ht_am)) + elog(ERROR, "cache lookup failed for access method %u", + idxrelrec->relam); + amrec = (Form_pg_am) GETSTRUCT(ht_am); + + /* Extract indcollation from the pg_index tuple */ + datum = SysCacheGetAttr(INDEXRELID, ht_idx, + Anum_pg_index_indcollation, &isnull); + Assert(!isnull); + indcollation = (oidvector *) DatumGetPointer(datum); + + /* Extract indclass from the pg_index tuple */ + datum = SysCacheGetAttr(INDEXRELID, ht_idx, + Anum_pg_index_indclass, &isnull); + Assert(!isnull); + indclass = (oidvector *) DatumGetPointer(datum); + + /* Begin building the IndexStmt */ + index = makeNode(IndexStmt); + index->relation = heapRel; + index->accessMethod = pstrdup(NameStr(amrec->amname)); + if (OidIsValid(idxrelrec->reltablespace)) + index->tableSpace = get_tablespace_name(idxrelrec->reltablespace); + else + index->tableSpace = NULL; + index->excludeOpNames = NIL; + index->idxcomment = NULL; + index->indexOid = InvalidOid; + index->oldNode = InvalidOid; + index->oldCreateSubid = InvalidSubTransactionId; + index->oldFirstRelfilenodeSubid = InvalidSubTransactionId; + index->unique = idxrec->indisunique; + index->primary = idxrec->indisprimary; + index->transformed = true; /* don't need transformIndexStmt */ + index->concurrent = false; + index->if_not_exists = false; + index->reset_default_tblspc = false; + + /* + * We don't try to preserve the name of the source index; instead, just + * let DefineIndex() choose a reasonable name. (If we tried to preserve + * the name, we'd get duplicate-relation-name failures unless the source + * table was in a different schema.) + */ + index->idxname = NULL; + + /* + * If the index is marked PRIMARY or has an exclusion condition, it's + * certainly from a constraint; else, if it's not marked UNIQUE, it + * certainly isn't. If it is or might be from a constraint, we have to + * fetch the pg_constraint record. + */ + if (index->primary || index->unique || idxrec->indisexclusion) + { + Oid constraintId = get_index_constraint(source_relid); + + if (OidIsValid(constraintId)) + { + HeapTuple ht_constr; + Form_pg_constraint conrec; + + if (constraintOid) + *constraintOid = constraintId; + + ht_constr = SearchSysCache1(CONSTROID, + ObjectIdGetDatum(constraintId)); + if (!HeapTupleIsValid(ht_constr)) + elog(ERROR, "cache lookup failed for constraint %u", + constraintId); + conrec = (Form_pg_constraint) GETSTRUCT(ht_constr); + + index->isconstraint = true; + index->deferrable = conrec->condeferrable; + index->initdeferred = conrec->condeferred; + + /* If it's an exclusion constraint, we need the operator names */ + if (idxrec->indisexclusion) + { + Datum *elems; + int nElems; + int i; + + Assert(conrec->contype == CONSTRAINT_EXCLUSION); + /* Extract operator OIDs from the pg_constraint tuple */ + datum = SysCacheGetAttr(CONSTROID, ht_constr, + Anum_pg_constraint_conexclop, + &isnull); + if (isnull) + elog(ERROR, "null conexclop for constraint %u", + constraintId); + + deconstruct_array(DatumGetArrayTypeP(datum), + OIDOID, sizeof(Oid), true, TYPALIGN_INT, + &elems, NULL, &nElems); + + for (i = 0; i < nElems; i++) + { + Oid operid = DatumGetObjectId(elems[i]); + HeapTuple opertup; + Form_pg_operator operform; + char *oprname; + char *nspname; + List *namelist; + + opertup = SearchSysCache1(OPEROID, + ObjectIdGetDatum(operid)); + if (!HeapTupleIsValid(opertup)) + elog(ERROR, "cache lookup failed for operator %u", + operid); + operform = (Form_pg_operator) GETSTRUCT(opertup); + oprname = pstrdup(NameStr(operform->oprname)); + /* For simplicity we always schema-qualify the op name */ + nspname = get_namespace_name(operform->oprnamespace); + namelist = list_make2(makeString(nspname), + makeString(oprname)); + index->excludeOpNames = lappend(index->excludeOpNames, + namelist); + ReleaseSysCache(opertup); + } + } + + ReleaseSysCache(ht_constr); + } + else + index->isconstraint = false; + } + else + index->isconstraint = false; + + /* Get the index expressions, if any */ + datum = SysCacheGetAttr(INDEXRELID, ht_idx, + Anum_pg_index_indexprs, &isnull); + if (!isnull) + { + char *exprsString; + + exprsString = TextDatumGetCString(datum); + indexprs = (List *) stringToNode(exprsString); + } + else + indexprs = NIL; + + /* Build the list of IndexElem */ + index->indexParams = NIL; + index->indexIncludingParams = NIL; + + indexpr_item = list_head(indexprs); + for (keyno = 0; keyno < idxrec->indnkeyatts; keyno++) + { + IndexElem *iparam; + AttrNumber attnum = idxrec->indkey.values[keyno]; + Form_pg_attribute attr = TupleDescAttr(RelationGetDescr(source_idx), + keyno); + int16 opt = source_idx->rd_indoption[keyno]; + + iparam = makeNode(IndexElem); + + if (AttributeNumberIsValid(attnum)) + { + /* Simple index column */ + char *attname; + + attname = get_attname(indrelid, attnum, false); + keycoltype = get_atttype(indrelid, attnum); + + iparam->name = attname; + iparam->expr = NULL; + } + else + { + /* Expressional index */ + Node *indexkey; + bool found_whole_row; + + if (indexpr_item == NULL) + elog(ERROR, "too few entries in indexprs list"); + indexkey = (Node *) lfirst(indexpr_item); + indexpr_item = lnext(indexprs, indexpr_item); + + /* Adjust Vars to match new table's column numbering */ + indexkey = map_variable_attnos(indexkey, + 1, 0, + attmap, + InvalidOid, &found_whole_row); + + /* As in expandTableLikeClause, reject whole-row variables */ + if (found_whole_row) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert whole-row table reference"), + errdetail("Index \"%s\" contains a whole-row table reference.", + RelationGetRelationName(source_idx)))); + + iparam->name = NULL; + iparam->expr = indexkey; + + keycoltype = exprType(indexkey); + } + + /* Copy the original index column name */ + iparam->indexcolname = pstrdup(NameStr(attr->attname)); + + /* Add the collation name, if non-default */ + iparam->collation = get_collation(indcollation->values[keyno], keycoltype); + + /* Add the operator class name, if non-default */ + iparam->opclass = get_opclass(indclass->values[keyno], keycoltype); + iparam->opclassopts = + untransformRelOptions(get_attoptions(source_relid, keyno + 1)); + + iparam->ordering = SORTBY_DEFAULT; + iparam->nulls_ordering = SORTBY_NULLS_DEFAULT; + + /* Adjust options if necessary */ + if (source_idx->rd_indam->amcanorder) + { + /* + * If it supports sort ordering, copy DESC and NULLS opts. Don't + * set non-default settings unnecessarily, though, so as to + * improve the chance of recognizing equivalence to constraint + * indexes. + */ + if (opt & INDOPTION_DESC) + { + iparam->ordering = SORTBY_DESC; + if ((opt & INDOPTION_NULLS_FIRST) == 0) + iparam->nulls_ordering = SORTBY_NULLS_LAST; + } + else + { + if (opt & INDOPTION_NULLS_FIRST) + iparam->nulls_ordering = SORTBY_NULLS_FIRST; + } + } + + index->indexParams = lappend(index->indexParams, iparam); + } + + /* Handle included columns separately */ + for (keyno = idxrec->indnkeyatts; keyno < idxrec->indnatts; keyno++) + { + IndexElem *iparam; + AttrNumber attnum = idxrec->indkey.values[keyno]; + Form_pg_attribute attr = TupleDescAttr(RelationGetDescr(source_idx), + keyno); + + iparam = makeNode(IndexElem); + + if (AttributeNumberIsValid(attnum)) + { + /* Simple index column */ + char *attname; + + attname = get_attname(indrelid, attnum, false); + + iparam->name = attname; + iparam->expr = NULL; + } + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("expressions are not supported in included columns"))); + + /* Copy the original index column name */ + iparam->indexcolname = pstrdup(NameStr(attr->attname)); + + index->indexIncludingParams = lappend(index->indexIncludingParams, iparam); + } + /* Copy reloptions if any */ + datum = SysCacheGetAttr(RELOID, ht_idxrel, + Anum_pg_class_reloptions, &isnull); + if (!isnull) + index->options = untransformRelOptions(datum); + + /* If it's a partial index, decompile and append the predicate */ + datum = SysCacheGetAttr(INDEXRELID, ht_idx, + Anum_pg_index_indpred, &isnull); + if (!isnull) + { + char *pred_str; + Node *pred_tree; + bool found_whole_row; + + /* Convert text string to node tree */ + pred_str = TextDatumGetCString(datum); + pred_tree = (Node *) stringToNode(pred_str); + + /* Adjust Vars to match new table's column numbering */ + pred_tree = map_variable_attnos(pred_tree, + 1, 0, + attmap, + InvalidOid, &found_whole_row); + + /* As in expandTableLikeClause, reject whole-row variables */ + if (found_whole_row) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert whole-row table reference"), + errdetail("Index \"%s\" contains a whole-row table reference.", + RelationGetRelationName(source_idx)))); + + index->whereClause = pred_tree; + } + + /* Clean up */ + ReleaseSysCache(ht_idxrel); + ReleaseSysCache(ht_am); + + return index; +} + +/* + * Generate a CreateStatsStmt node using information from an already existing + * extended statistic "source_statsid", for the rel identified by heapRel and + * heapRelid. + */ +static CreateStatsStmt * +generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid, + Oid source_statsid) +{ + HeapTuple ht_stats; + Form_pg_statistic_ext statsrec; + CreateStatsStmt *stats; + List *stat_types = NIL; + List *def_names = NIL; + bool isnull; + Datum datum; + ArrayType *arr; + char *enabled; + int i; + + Assert(OidIsValid(heapRelid)); + Assert(heapRel != NULL); + + /* + * Fetch pg_statistic_ext tuple of source statistics object. + */ + ht_stats = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(source_statsid)); + if (!HeapTupleIsValid(ht_stats)) + elog(ERROR, "cache lookup failed for statistics object %u", source_statsid); + statsrec = (Form_pg_statistic_ext) GETSTRUCT(ht_stats); + + /* Determine which statistics types exist */ + datum = SysCacheGetAttr(STATEXTOID, ht_stats, + Anum_pg_statistic_ext_stxkind, &isnull); + Assert(!isnull); + arr = DatumGetArrayTypeP(datum); + if (ARR_NDIM(arr) != 1 || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != CHAROID) + elog(ERROR, "stxkind is not a 1-D char array"); + enabled = (char *) ARR_DATA_PTR(arr); + for (i = 0; i < ARR_DIMS(arr)[0]; i++) + { + if (enabled[i] == STATS_EXT_NDISTINCT) + stat_types = lappend(stat_types, makeString("ndistinct")); + else if (enabled[i] == STATS_EXT_DEPENDENCIES) + stat_types = lappend(stat_types, makeString("dependencies")); + else if (enabled[i] == STATS_EXT_MCV) + stat_types = lappend(stat_types, makeString("mcv")); + else if (enabled[i] == STATS_EXT_EXPRESSIONS) + /* expression stats are not exposed to users */ + continue; + else + elog(ERROR, "unrecognized statistics kind %c", enabled[i]); + } + + /* Determine which columns the statistics are on */ + for (i = 0; i < statsrec->stxkeys.dim1; i++) + { + StatsElem *selem = makeNode(StatsElem); + AttrNumber attnum = statsrec->stxkeys.values[i]; + + selem->name = get_attname(heapRelid, attnum, false); + selem->expr = NULL; + + def_names = lappend(def_names, selem); + } + + /* + * Now handle expressions, if there are any. The order (with respect to + * regular attributes) does not really matter for extended stats, so we + * simply append them after simple column references. + * + * XXX Some places during build/estimation treat expressions as if they + * are before attributes, but for the CREATE command that's entirely + * irrelevant. + */ + datum = SysCacheGetAttr(STATEXTOID, ht_stats, + Anum_pg_statistic_ext_stxexprs, &isnull); + + if (!isnull) + { + ListCell *lc; + List *exprs = NIL; + char *exprsString; + + exprsString = TextDatumGetCString(datum); + exprs = (List *) stringToNode(exprsString); + + foreach(lc, exprs) + { + StatsElem *selem = makeNode(StatsElem); + + selem->name = NULL; + selem->expr = (Node *) lfirst(lc); + + def_names = lappend(def_names, selem); + } + + pfree(exprsString); + } + + /* finally, build the output node */ + stats = makeNode(CreateStatsStmt); + stats->defnames = NULL; + stats->stat_types = stat_types; + stats->exprs = def_names; + stats->relations = list_make1(heapRel); + stats->stxcomment = NULL; + stats->transformed = true; /* don't need transformStatsStmt again */ + stats->if_not_exists = false; + + /* Clean up */ + ReleaseSysCache(ht_stats); + + return stats; +} + +/* + * get_collation - fetch qualified name of a collation + * + * If collation is InvalidOid or is the default for the given actual_datatype, + * then the return value is NIL. + */ +static List * +get_collation(Oid collation, Oid actual_datatype) +{ + List *result; + HeapTuple ht_coll; + Form_pg_collation coll_rec; + char *nsp_name; + char *coll_name; + + if (!OidIsValid(collation)) + return NIL; /* easy case */ + if (collation == get_typcollation(actual_datatype)) + return NIL; /* just let it default */ + + ht_coll = SearchSysCache1(COLLOID, ObjectIdGetDatum(collation)); + if (!HeapTupleIsValid(ht_coll)) + elog(ERROR, "cache lookup failed for collation %u", collation); + coll_rec = (Form_pg_collation) GETSTRUCT(ht_coll); + + /* For simplicity, we always schema-qualify the name */ + nsp_name = get_namespace_name(coll_rec->collnamespace); + coll_name = pstrdup(NameStr(coll_rec->collname)); + result = list_make2(makeString(nsp_name), makeString(coll_name)); + + ReleaseSysCache(ht_coll); + return result; +} + +/* + * get_opclass - fetch qualified name of an index operator class + * + * If the opclass is the default for the given actual_datatype, then + * the return value is NIL. + */ +static List * +get_opclass(Oid opclass, Oid actual_datatype) +{ + List *result = NIL; + HeapTuple ht_opc; + Form_pg_opclass opc_rec; + + ht_opc = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass)); + if (!HeapTupleIsValid(ht_opc)) + elog(ERROR, "cache lookup failed for opclass %u", opclass); + opc_rec = (Form_pg_opclass) GETSTRUCT(ht_opc); + + if (GetDefaultOpClass(actual_datatype, opc_rec->opcmethod) != opclass) + { + /* For simplicity, we always schema-qualify the name */ + char *nsp_name = get_namespace_name(opc_rec->opcnamespace); + char *opc_name = pstrdup(NameStr(opc_rec->opcname)); + + result = list_make2(makeString(nsp_name), makeString(opc_name)); + } + + ReleaseSysCache(ht_opc); + return result; +} + + +/* + * transformIndexConstraints + * Handle UNIQUE, PRIMARY KEY, EXCLUDE constraints, which create indexes. + * We also merge in any index definitions arising from + * LIKE ... INCLUDING INDEXES. + */ +static void +transformIndexConstraints(CreateStmtContext *cxt) +{ + IndexStmt *index; + List *indexlist = NIL; + List *finalindexlist = NIL; + ListCell *lc; + + /* + * 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 or + * EXCLUDE, create an index as for PRIMARY KEY, but do not insist on NOT + * NULL. + */ + foreach(lc, cxt->ixconstraints) + { + Constraint *constraint = lfirst_node(Constraint, lc); + + Assert(constraint->contype == CONSTR_PRIMARY || + constraint->contype == CONSTR_UNIQUE || + constraint->contype == CONSTR_EXCLUSION); + + index = transformIndexConstraint(constraint, cxt); + + 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 SQL 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. + */ + if (cxt->pkey != NULL) + { + /* Make sure we keep the PKEY index in preference to others... */ + finalindexlist = list_make1(cxt->pkey); + } + + foreach(lc, indexlist) + { + bool keep = true; + ListCell *k; + + index = lfirst(lc); + + /* if it's pkey, it's already in finalindexlist */ + if (index == cxt->pkey) + continue; + + foreach(k, finalindexlist) + { + IndexStmt *priorindex = lfirst(k); + + if (equal(index->indexParams, priorindex->indexParams) && + equal(index->indexIncludingParams, priorindex->indexIncludingParams) && + equal(index->whereClause, priorindex->whereClause) && + equal(index->excludeOpNames, priorindex->excludeOpNames) && + strcmp(index->accessMethod, priorindex->accessMethod) == 0 && + index->deferrable == priorindex->deferrable && + index->initdeferred == priorindex->initdeferred) + { + priorindex->unique |= index->unique; + + /* + * 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) + finalindexlist = lappend(finalindexlist, index); + } + + /* + * Now append all the IndexStmts to cxt->alist. If we generated an ALTER + * TABLE SET NOT NULL statement to support a primary key, it's already in + * cxt->alist. + */ + cxt->alist = list_concat(cxt->alist, finalindexlist); +} + +/* + * transformIndexConstraint + * Transform one UNIQUE, PRIMARY KEY, or EXCLUDE constraint for + * transformIndexConstraints. + * + * We return an IndexStmt. For a PRIMARY KEY constraint, we additionally + * produce NOT NULL constraints, either by marking ColumnDefs in cxt->columns + * as is_not_null or by adding an ALTER TABLE SET NOT NULL command to + * cxt->alist. + */ +static IndexStmt * +transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt) +{ + IndexStmt *index; + List *notnullcmds = NIL; + ListCell *lc; + + index = makeNode(IndexStmt); + + index->unique = (constraint->contype != CONSTR_EXCLUSION); + 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), + parser_errposition(cxt->pstate, constraint->location))); + cxt->pkey = index; + + /* + * In ALTER TABLE case, a primary index might already exist, but + * DefineIndex will check for it. + */ + } + index->isconstraint = true; + index->deferrable = constraint->deferrable; + index->initdeferred = constraint->initdeferred; + + if (constraint->conname != NULL) + index->idxname = pstrdup(constraint->conname); + else + index->idxname = NULL; /* DefineIndex will choose name */ + + index->relation = cxt->relation; + index->accessMethod = constraint->access_method ? constraint->access_method : DEFAULT_INDEX_TYPE; + index->options = constraint->options; + index->tableSpace = constraint->indexspace; + index->whereClause = constraint->where_clause; + index->indexParams = NIL; + index->indexIncludingParams = NIL; + index->excludeOpNames = NIL; + index->idxcomment = NULL; + index->indexOid = InvalidOid; + index->oldNode = InvalidOid; + index->oldCreateSubid = InvalidSubTransactionId; + index->oldFirstRelfilenodeSubid = InvalidSubTransactionId; + index->transformed = false; + index->concurrent = false; + index->if_not_exists = false; + index->reset_default_tblspc = constraint->reset_default_tblspc; + + /* + * If it's ALTER TABLE ADD CONSTRAINT USING INDEX, look up the index and + * verify it's usable, then extract the implied column name list. (We + * will not actually need the column name list at runtime, but we need it + * now to check for duplicate column entries below.) + */ + if (constraint->indexname != NULL) + { + char *index_name = constraint->indexname; + Relation heap_rel = cxt->rel; + Oid index_oid; + Relation index_rel; + Form_pg_index index_form; + oidvector *indclass; + Datum indclassDatum; + bool isnull; + int i; + + /* Grammar should not allow this with explicit column list */ + Assert(constraint->keys == NIL); + + /* Grammar should only allow PRIMARY and UNIQUE constraints */ + Assert(constraint->contype == CONSTR_PRIMARY || + constraint->contype == CONSTR_UNIQUE); + + /* Must be ALTER, not CREATE, but grammar doesn't enforce that */ + if (!cxt->isalter) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot use an existing index in CREATE TABLE"), + parser_errposition(cxt->pstate, constraint->location))); + + /* Look for the index in the same schema as the table */ + index_oid = get_relname_relid(index_name, RelationGetNamespace(heap_rel)); + + if (!OidIsValid(index_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("index \"%s\" does not exist", index_name), + parser_errposition(cxt->pstate, constraint->location))); + + /* Open the index (this will throw an error if it is not an index) */ + index_rel = index_open(index_oid, AccessShareLock); + index_form = index_rel->rd_index; + + /* Check that it does not have an associated constraint already */ + if (OidIsValid(get_index_constraint(index_oid))) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("index \"%s\" is already associated with a constraint", + index_name), + parser_errposition(cxt->pstate, constraint->location))); + + /* Perform validity checks on the index */ + if (index_form->indrelid != RelationGetRelid(heap_rel)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("index \"%s\" does not belong to table \"%s\"", + index_name, RelationGetRelationName(heap_rel)), + parser_errposition(cxt->pstate, constraint->location))); + + if (!index_form->indisvalid) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("index \"%s\" is not valid", index_name), + parser_errposition(cxt->pstate, constraint->location))); + + if (!index_form->indisunique) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a unique index", index_name), + errdetail("Cannot create a primary key or unique constraint using such an index."), + parser_errposition(cxt->pstate, constraint->location))); + + if (RelationGetIndexExpressions(index_rel) != NIL) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("index \"%s\" contains expressions", index_name), + errdetail("Cannot create a primary key or unique constraint using such an index."), + parser_errposition(cxt->pstate, constraint->location))); + + if (RelationGetIndexPredicate(index_rel) != NIL) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is a partial index", index_name), + errdetail("Cannot create a primary key or unique constraint using such an index."), + parser_errposition(cxt->pstate, constraint->location))); + + /* + * It's probably unsafe to change a deferred index to non-deferred. (A + * non-constraint index couldn't be deferred anyway, so this case + * should never occur; no need to sweat, but let's check it.) + */ + if (!index_form->indimmediate && !constraint->deferrable) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is a deferrable index", index_name), + errdetail("Cannot create a non-deferrable constraint using a deferrable index."), + parser_errposition(cxt->pstate, constraint->location))); + + /* + * Insist on it being a btree. That's the only kind that supports + * uniqueness at the moment anyway; but we must have an index that + * exactly matches what you'd get from plain ADD CONSTRAINT syntax, + * else dump and reload will produce a different index (breaking + * pg_upgrade in particular). + */ + if (index_rel->rd_rel->relam != get_index_am_oid(DEFAULT_INDEX_TYPE, false)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("index \"%s\" is not a btree", index_name), + parser_errposition(cxt->pstate, constraint->location))); + + /* Must get indclass the hard way */ + indclassDatum = SysCacheGetAttr(INDEXRELID, index_rel->rd_indextuple, + Anum_pg_index_indclass, &isnull); + Assert(!isnull); + indclass = (oidvector *) DatumGetPointer(indclassDatum); + + for (i = 0; i < index_form->indnatts; i++) + { + int16 attnum = index_form->indkey.values[i]; + const FormData_pg_attribute *attform; + char *attname; + Oid defopclass; + + /* + * We shouldn't see attnum == 0 here, since we already rejected + * expression indexes. If we do, SystemAttributeDefinition will + * throw an error. + */ + if (attnum > 0) + { + Assert(attnum <= heap_rel->rd_att->natts); + attform = TupleDescAttr(heap_rel->rd_att, attnum - 1); + } + else + attform = SystemAttributeDefinition(attnum); + attname = pstrdup(NameStr(attform->attname)); + + if (i < index_form->indnkeyatts) + { + /* + * Insist on default opclass, collation, and sort options. + * While the index would still work as a constraint with + * non-default settings, it might not provide exactly the same + * uniqueness semantics as you'd get from a normally-created + * constraint; and there's also the dump/reload problem + * mentioned above. + */ + Datum attoptions = + get_attoptions(RelationGetRelid(index_rel), i + 1); + + defopclass = GetDefaultOpClass(attform->atttypid, + index_rel->rd_rel->relam); + if (indclass->values[i] != defopclass || + attform->attcollation != index_rel->rd_indcollation[i] || + attoptions != (Datum) 0 || + index_rel->rd_indoption[i] != 0) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("index \"%s\" column number %d does not have default sorting behavior", index_name, i + 1), + errdetail("Cannot create a primary key or unique constraint using such an index."), + parser_errposition(cxt->pstate, constraint->location))); + + constraint->keys = lappend(constraint->keys, makeString(attname)); + } + else + constraint->including = lappend(constraint->including, makeString(attname)); + } + + /* Close the index relation but keep the lock */ + relation_close(index_rel, NoLock); + + index->indexOid = index_oid; + } + + /* + * If it's an EXCLUDE constraint, the grammar returns a list of pairs of + * IndexElems and operator names. We have to break that apart into + * separate lists. + */ + if (constraint->contype == CONSTR_EXCLUSION) + { + foreach(lc, constraint->exclusions) + { + List *pair = (List *) lfirst(lc); + IndexElem *elem; + List *opname; + + Assert(list_length(pair) == 2); + elem = linitial_node(IndexElem, pair); + opname = lsecond_node(List, pair); + + index->indexParams = lappend(index->indexParams, elem); + index->excludeOpNames = lappend(index->excludeOpNames, opname); + } + } + + /* + * For UNIQUE and PRIMARY KEY, we just have a list of column names. + * + * Make sure referenced keys exist. If we are making a PRIMARY KEY index, + * also make sure they are NOT NULL. + */ + else + { + foreach(lc, constraint->keys) + { + char *key = strVal(lfirst(lc)); + bool found = false; + bool forced_not_null = false; + ColumnDef *column = NULL; + ListCell *columns; + IndexElem *iparam; + + /* Make sure referenced column exists. */ + foreach(columns, cxt->columns) + { + column = castNode(ColumnDef, lfirst(columns)); + if (strcmp(column->colname, key) == 0) + { + found = true; + break; + } + } + if (found) + { + /* + * column is defined in the new table. For PRIMARY KEY, we + * can apply the NOT NULL constraint cheaply here ... unless + * the column is marked is_from_type, in which case marking it + * here would be ineffective (see MergeAttributes). + */ + if (constraint->contype == CONSTR_PRIMARY && + !column->is_from_type) + { + column->is_not_null = true; + forced_not_null = true; + } + } + else if (SystemAttributeByName(key) != 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 = castNode(RangeVar, lfirst(inher)); + Relation rel; + int count; + + rel = table_openrv(inh, AccessShareLock); + /* check user requested inheritance from valid relkind */ + if (rel->rd_rel->relkind != RELKIND_RELATION && + rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE && + rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("inherited relation \"%s\" is not a table or foreign table", + inh->relname))); + for (count = 0; count < rel->rd_att->natts; count++) + { + Form_pg_attribute inhattr = TupleDescAttr(rel->rd_att, + count); + char *inhname = NameStr(inhattr->attname); + + if (inhattr->attisdropped) + continue; + if (strcmp(key, inhname) == 0) + { + found = true; + + /* + * It's tempting to set forced_not_null if the + * parent column is already NOT NULL, but that + * seems unsafe because the column's NOT NULL + * marking might disappear between now and + * execution. Do the runtime check to be safe. + */ + break; + } + } + table_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. + */ + if (!found && !cxt->isalter) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" named in key does not exist", key), + parser_errposition(cxt->pstate, constraint->location))); + + /* 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), + parser_errposition(cxt->pstate, constraint->location))); + else + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_COLUMN), + errmsg("column \"%s\" appears twice in unique constraint", + key), + parser_errposition(cxt->pstate, constraint->location))); + } + } + + /* OK, add it to the index definition */ + iparam = makeNode(IndexElem); + iparam->name = pstrdup(key); + iparam->expr = NULL; + iparam->indexcolname = NULL; + iparam->collation = NIL; + iparam->opclass = NIL; + iparam->opclassopts = NIL; + iparam->ordering = SORTBY_DEFAULT; + iparam->nulls_ordering = SORTBY_NULLS_DEFAULT; + index->indexParams = lappend(index->indexParams, iparam); + + /* + * For a primary-key column, also create an item for ALTER TABLE + * SET NOT NULL if we couldn't ensure it via is_not_null above. + */ + if (constraint->contype == CONSTR_PRIMARY && !forced_not_null) + { + AlterTableCmd *notnullcmd = makeNode(AlterTableCmd); + + notnullcmd->subtype = AT_SetNotNull; + notnullcmd->name = pstrdup(key); + notnullcmds = lappend(notnullcmds, notnullcmd); + } + } + } + + /* + * Add included columns to index definition. This is much like the + * simple-column-name-list code above, except that we don't worry about + * NOT NULL marking; included columns in a primary key should not be + * forced NOT NULL. We don't complain about duplicate columns, either, + * though maybe we should? + */ + foreach(lc, constraint->including) + { + char *key = strVal(lfirst(lc)); + bool found = false; + ColumnDef *column = NULL; + ListCell *columns; + IndexElem *iparam; + + foreach(columns, cxt->columns) + { + column = lfirst_node(ColumnDef, columns); + if (strcmp(column->colname, key) == 0) + { + found = true; + break; + } + } + + if (!found) + { + if (SystemAttributeByName(key) != NULL) + { + /* + * column will be a system column in the new table, so accept + * it. + */ + found = true; + } + else if (cxt->inhRelations) + { + /* try inherited tables */ + ListCell *inher; + + foreach(inher, cxt->inhRelations) + { + RangeVar *inh = lfirst_node(RangeVar, inher); + Relation rel; + int count; + + rel = table_openrv(inh, AccessShareLock); + /* check user requested inheritance from valid relkind */ + if (rel->rd_rel->relkind != RELKIND_RELATION && + rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE && + rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("inherited relation \"%s\" is not a table or foreign table", + inh->relname))); + for (count = 0; count < rel->rd_att->natts; count++) + { + Form_pg_attribute inhattr = TupleDescAttr(rel->rd_att, + count); + char *inhname = NameStr(inhattr->attname); + + if (inhattr->attisdropped) + continue; + if (strcmp(key, inhname) == 0) + { + found = true; + break; + } + } + table_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. + */ + if (!found && !cxt->isalter) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" named in key does not exist", key), + parser_errposition(cxt->pstate, constraint->location))); + + /* OK, add it to the index definition */ + iparam = makeNode(IndexElem); + iparam->name = pstrdup(key); + iparam->expr = NULL; + iparam->indexcolname = NULL; + iparam->collation = NIL; + iparam->opclass = NIL; + iparam->opclassopts = NIL; + index->indexIncludingParams = lappend(index->indexIncludingParams, iparam); + } + + /* + * If we found anything that requires run-time SET NOT NULL, build a full + * ALTER TABLE command for that and add it to cxt->alist. + */ + if (notnullcmds) + { + AlterTableStmt *alterstmt = makeNode(AlterTableStmt); + + alterstmt->relation = copyObject(cxt->relation); + alterstmt->cmds = notnullcmds; + alterstmt->objtype = OBJECT_TABLE; + alterstmt->missing_ok = false; + + cxt->alist = lappend(cxt->alist, alterstmt); + } + + return index; +} + +/* + * transformExtendedStatistics + * Handle extended statistic objects + * + * Right now, there's nothing to do here, so we just append the list to + * the existing "after" list. + */ +static void +transformExtendedStatistics(CreateStmtContext *cxt) +{ + cxt->alist = list_concat(cxt->alist, cxt->extstats); +} + +/* + * transformCheckConstraints + * handle CHECK constraints + * + * Right now, there's nothing to do here when called from ALTER TABLE, + * but the other constraint-transformation functions are called in both + * the CREATE TABLE and ALTER TABLE paths, so do the same here, and just + * don't do anything if we're not authorized to skip validation. + */ +static void +transformCheckConstraints(CreateStmtContext *cxt, bool skipValidation) +{ + ListCell *ckclist; + + if (cxt->ckconstraints == NIL) + return; + + /* + * If creating a new table (but not a foreign table), we can safely skip + * validation of check constraints, and nonetheless mark them valid. (This + * will override any user-supplied NOT VALID flag.) + */ + if (skipValidation) + { + foreach(ckclist, cxt->ckconstraints) + { + Constraint *constraint = (Constraint *) lfirst(ckclist); + + constraint->skip_validation = true; + constraint->initially_valid = true; + } + } +} + +/* + * transformFKConstraints + * handle FOREIGN KEY constraints + */ +static void +transformFKConstraints(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 FK constraints, and nonetheless mark them valid. + * (This will override any user-supplied NOT VALID flag.) + */ + if (skipValidation) + { + foreach(fkclist, cxt->fkconstraints) + { + Constraint *constraint = (Constraint *) lfirst(fkclist); + + constraint->skip_validation = true; + constraint->initially_valid = 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. See also the handling of cxt->likeclauses. + */ + if (!isAddConstraint) + { + AlterTableStmt *alterstmt = makeNode(AlterTableStmt); + + alterstmt->relation = cxt->relation; + alterstmt->cmds = NIL; + alterstmt->objtype = OBJECT_TABLE; + + foreach(fkclist, cxt->fkconstraints) + { + Constraint *constraint = (Constraint *) lfirst(fkclist); + AlterTableCmd *altercmd = makeNode(AlterTableCmd); + + altercmd->subtype = AT_AddConstraint; + altercmd->name = NULL; + altercmd->def = (Node *) constraint; + alterstmt->cmds = lappend(alterstmt->cmds, altercmd); + } + + cxt->alist = lappend(cxt->alist, alterstmt); + } +} + +/* + * transformIndexStmt - parse analysis for CREATE INDEX and ALTER TABLE + * + * 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. + * + * To avoid race conditions, it's important that this function rely only on + * the passed-in relid (and not on stmt->relation) to determine the target + * relation. + */ +IndexStmt * +transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) +{ + ParseState *pstate; + ParseNamespaceItem *nsitem; + ListCell *l; + Relation rel; + + /* Nothing to do if statement already transformed. */ + if (stmt->transformed) + return stmt; + + /* 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. Caller is responsible for locking + * relation, but we still need to open it. + */ + rel = relation_open(relid, NoLock); + nsitem = addRangeTableEntryForRelation(pstate, rel, + AccessShareLock, + NULL, false, true); + + /* no to join list, yes to namespaces */ + addNSItemToQuery(pstate, nsitem, false, true, true); + + /* take care of the where clause */ + if (stmt->whereClause) + { + stmt->whereClause = transformWhereClause(pstate, + stmt->whereClause, + EXPR_KIND_INDEX_PREDICATE, + "WHERE"); + /* we have to fix its collations too */ + assign_expr_collations(pstate, stmt->whereClause); + } + + /* take care of any index expressions */ + foreach(l, stmt->indexParams) + { + IndexElem *ielem = (IndexElem *) lfirst(l); + + if (ielem->expr) + { + /* Extract preliminary index col name before transforming expr */ + if (ielem->indexcolname == NULL) + ielem->indexcolname = FigureIndexColname(ielem->expr); + + /* Now do parse transformation of the expression */ + ielem->expr = transformExpr(pstate, ielem->expr, + EXPR_KIND_INDEX_EXPRESSION); + + /* We have to fix its collations too */ + assign_expr_collations(pstate, ielem->expr); + + /* + * transformExpr() should have already rejected subqueries, + * aggregates, window functions, and SRFs, based on the EXPR_KIND_ + * for an index expression. + * + * DefineIndex() will make more checks. + */ + } + } + + /* + * Check that only the base rel is mentioned. (This should be dead code + * now that add_missing_from is history.) + */ + 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 */ + table_close(rel, NoLock); + + /* Mark statement as successfully transformed */ + stmt->transformed = true; + + return stmt; +} + +/* + * transformStatsStmt - parse analysis for CREATE STATISTICS + * + * To avoid race conditions, it's important that this function relies only on + * the passed-in relid (and not on stmt->relation) to determine the target + * relation. + */ +CreateStatsStmt * +transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +{ + ParseState *pstate; + ParseNamespaceItem *nsitem; + ListCell *l; + Relation rel; + + /* Nothing to do if statement already transformed. */ + if (stmt->transformed) + return stmt; + + /* 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. Caller is responsible for locking + * relation, but we still need to open it. + */ + rel = relation_open(relid, NoLock); + nsitem = addRangeTableEntryForRelation(pstate, rel, + AccessShareLock, + NULL, false, true); + + /* no to join list, yes to namespaces */ + addNSItemToQuery(pstate, nsitem, false, true, true); + + /* take care of any expressions */ + foreach(l, stmt->exprs) + { + StatsElem *selem = (StatsElem *) lfirst(l); + + if (selem->expr) + { + /* Now do parse transformation of the expression */ + selem->expr = transformExpr(pstate, selem->expr, + EXPR_KIND_STATS_EXPRESSION); + + /* We have to fix its collations too */ + assign_expr_collations(pstate, selem->expr); + } + } + + /* + * Check that only the base rel is mentioned. (This should be dead code + * now that add_missing_from is history.) + */ + if (list_length(pstate->p_rtable) != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("statistics expressions can refer only to the table being referenced"))); + + free_parsestate(pstate); + + /* Close relation */ + table_close(rel, NoLock); + + /* Mark statement as successfully transformed */ + stmt->transformed = true; + + 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. + */ +void +transformRuleStmt(RuleStmt *stmt, const char *queryString, + List **actions, Node **whereClause) +{ + Relation rel; + ParseState *pstate; + ParseNamespaceItem *oldnsitem; + ParseNamespaceItem *newnsitem; + + /* + * 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 = table_openrv(stmt->relation, AccessExclusiveLock); + + if (rel->rd_rel->relkind == RELKIND_MATVIEW) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("rules on materialized views are not supported"))); + + /* 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 ParseNamespaceItems in the main pstate for use in parsing + * the rule qualification. + */ + oldnsitem = addRangeTableEntryForRelation(pstate, rel, + AccessShareLock, + makeAlias("old", NIL), + false, false); + newnsitem = addRangeTableEntryForRelation(pstate, rel, + AccessShareLock, + makeAlias("new", NIL), + false, false); + /* Must override addRangeTableEntry's default access-check flags */ + oldnsitem->p_rte->requiredPerms = 0; + newnsitem->p_rte->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: + addNSItemToQuery(pstate, oldnsitem, false, true, true); + break; + case CMD_UPDATE: + addNSItemToQuery(pstate, oldnsitem, false, true, true); + addNSItemToQuery(pstate, newnsitem, false, true, true); + break; + case CMD_INSERT: + addNSItemToQuery(pstate, newnsitem, false, true, true); + break; + case CMD_DELETE: + addNSItemToQuery(pstate, oldnsitem, false, true, true); + break; + default: + elog(ERROR, "unrecognized event type: %d", + (int) stmt->event); + break; + } + + /* take care of the where clause */ + *whereClause = transformWhereClause(pstate, + stmt->whereClause, + EXPR_KIND_WHERE, + "WHERE"); + /* we have to fix its collations too */ + assign_expr_collations(pstate, *whereClause); + + /* this is probably dead code without add_missing_from: */ + 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"))); + + /* + * '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. + */ + oldnsitem = addRangeTableEntryForRelation(sub_pstate, rel, + AccessShareLock, + makeAlias("old", NIL), + false, false); + newnsitem = addRangeTableEntryForRelation(sub_pstate, rel, + AccessShareLock, + makeAlias("new", NIL), + false, false); + oldnsitem->p_rte->requiredPerms = 0; + newnsitem->p_rte->requiredPerms = 0; + addNSItemToQuery(sub_pstate, oldnsitem, false, true, false); + addNSItemToQuery(sub_pstate, newnsitem, false, true, false); + + /* Transform the rule action statement */ + top_subqry = transformStmt(sub_pstate, 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; + } + + /* + * OLD/NEW are not allowed in WITH queries, because they would + * amount to outer references for the WITH, which we disallow. + * However, they were already in the outer rangetable when we + * analyzed the query, so we have to check. + * + * Note that in the INSERT...SELECT case, we need to examine the + * CTE lists of both top_subqry and sub_qry. + * + * Note that we aren't digging into the body of the query looking + * for WITHs in nested sub-SELECTs. A WITH down there can + * legitimately refer to OLD/NEW, because it'd be an + * indirect-correlated outer reference. + */ + if (rangeTableEntry_used((Node *) top_subqry->cteList, + PRS2_OLD_VARNO, 0) || + rangeTableEntry_used((Node *) sub_qry->cteList, + PRS2_OLD_VARNO, 0)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot refer to OLD within WITH query"))); + if (rangeTableEntry_used((Node *) top_subqry->cteList, + PRS2_NEW_VARNO, 0) || + rangeTableEntry_used((Node *) sub_qry->cteList, + PRS2_NEW_VARNO, 0)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot refer to NEW within WITH query"))); + + /* + * 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)) + { + RangeTblRef *rtr; + + /* + * 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"))); + /* hackishly add OLD to the already-built FROM clause */ + rtr = makeNode(RangeTblRef); + rtr->rtindex = oldnsitem->p_rtindex; + sub_qry->jointree->fromlist = + lappend(sub_qry->jointree->fromlist, rtr); + } + + newactions = lappend(newactions, top_subqry); + + free_parsestate(sub_pstate); + } + + *actions = newactions; + } + + free_parsestate(pstate); + + /* Close relation, but keep the exclusive lock */ + table_close(rel, NoLock); +} + + +/* + * transformAlterTableStmt - + * parse analysis for ALTER TABLE + * + * Returns the transformed AlterTableStmt. There may be additional actions + * to be done before and after the transformed statement, which are returned + * in *beforeStmts and *afterStmts as lists of utility command parsetrees. + * + * To avoid race conditions, it's important that this function rely only on + * the passed-in relid (and not on stmt->relation) to determine the target + * relation. + */ +AlterTableStmt * +transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, + const char *queryString, + List **beforeStmts, List **afterStmts) +{ + Relation rel; + TupleDesc tupdesc; + ParseState *pstate; + CreateStmtContext cxt; + List *save_alist; + ListCell *lcmd, + *l; + List *newcmds = NIL; + bool skipValidation = true; + AlterTableCmd *newcmd; + ParseNamespaceItem *nsitem; + + /* Caller is responsible for locking the relation */ + rel = relation_open(relid, NoLock); + tupdesc = RelationGetDescr(rel); + + /* Set up pstate */ + pstate = make_parsestate(NULL); + pstate->p_sourcetext = queryString; + nsitem = addRangeTableEntryForRelation(pstate, + rel, + AccessShareLock, + NULL, + false, + true); + addNSItemToQuery(pstate, nsitem, false, true, true); + + /* Set up CreateStmtContext */ + cxt.pstate = pstate; + if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) + { + cxt.stmtType = "ALTER FOREIGN TABLE"; + cxt.isforeign = true; + } + else + { + cxt.stmtType = "ALTER TABLE"; + cxt.isforeign = false; + } + cxt.relation = stmt->relation; + cxt.rel = rel; + cxt.inhRelations = NIL; + cxt.isalter = true; + cxt.columns = NIL; + cxt.ckconstraints = NIL; + cxt.fkconstraints = NIL; + cxt.ixconstraints = NIL; + cxt.likeclauses = NIL; + cxt.extstats = NIL; + cxt.blist = NIL; + cxt.alist = NIL; + cxt.pkey = NULL; + cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); + cxt.partbound = NULL; + cxt.ofType = false; + + /* + * Transform ALTER subcommands that need it (most don't). These largely + * re-use code from CREATE TABLE. + */ + foreach(lcmd, stmt->cmds) + { + AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd); + + switch (cmd->subtype) + { + case AT_AddColumn: + case AT_AddColumnRecurse: + { + ColumnDef *def = castNode(ColumnDef, cmd->def); + + transformColumnDefinition(&cxt, def); + + /* + * If the column has a non-null default, we can't skip + * validation of foreign keys. + */ + if (def->raw_default != NULL) + skipValidation = false; + + /* + * All constraints are processed in other ways. Remove the + * original list + */ + def->constraints = NIL; + + newcmds = lappend(newcmds, cmd); + break; + } + + case AT_AddConstraint: + case AT_AddConstraintRecurse: + + /* + * The original AddConstraint cmd node doesn't go to newcmds + */ + if (IsA(cmd->def, Constraint)) + { + transformTableConstraint(&cxt, (Constraint *) cmd->def); + if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN) + skipValidation = false; + } + else + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(cmd->def)); + break; + + case AT_AlterColumnType: + { + ColumnDef *def = castNode(ColumnDef, cmd->def); + AttrNumber attnum; + + /* + * For ALTER COLUMN TYPE, transform the USING clause if + * one was specified. + */ + if (def->raw_default) + { + def->cooked_default = + transformExpr(pstate, def->raw_default, + EXPR_KIND_ALTER_COL_TRANSFORM); + } + + /* + * For identity column, create ALTER SEQUENCE command to + * change the data type of the sequence. + */ + attnum = get_attnum(relid, cmd->name); + if (attnum == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + cmd->name, RelationGetRelationName(rel)))); + + if (attnum > 0 && + TupleDescAttr(tupdesc, attnum - 1)->attidentity) + { + Oid seq_relid = getIdentitySequence(relid, attnum, false); + Oid typeOid = typenameTypeId(pstate, def->typeName); + AlterSeqStmt *altseqstmt = makeNode(AlterSeqStmt); + + altseqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)), + get_rel_name(seq_relid), + -1); + altseqstmt->options = list_make1(makeDefElem("as", (Node *) makeTypeNameFromOid(typeOid, -1), -1)); + altseqstmt->for_identity = true; + cxt.blist = lappend(cxt.blist, altseqstmt); + } + + newcmds = lappend(newcmds, cmd); + break; + } + + case AT_AddIdentity: + { + Constraint *def = castNode(Constraint, cmd->def); + ColumnDef *newdef = makeNode(ColumnDef); + AttrNumber attnum; + + newdef->colname = cmd->name; + newdef->identity = def->generated_when; + cmd->def = (Node *) newdef; + + attnum = get_attnum(relid, cmd->name); + if (attnum == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + cmd->name, RelationGetRelationName(rel)))); + + generateSerialExtraStmts(&cxt, newdef, + get_atttype(relid, attnum), + def->options, true, true, + NULL, NULL); + + newcmds = lappend(newcmds, cmd); + break; + } + + case AT_SetIdentity: + { + /* + * Create an ALTER SEQUENCE statement for the internal + * sequence of the identity column. + */ + ListCell *lc; + List *newseqopts = NIL; + List *newdef = NIL; + AttrNumber attnum; + Oid seq_relid; + + /* + * Split options into those handled by ALTER SEQUENCE and + * those for ALTER TABLE proper. + */ + foreach(lc, castNode(List, cmd->def)) + { + DefElem *def = lfirst_node(DefElem, lc); + + if (strcmp(def->defname, "generated") == 0) + newdef = lappend(newdef, def); + else + newseqopts = lappend(newseqopts, def); + } + + attnum = get_attnum(relid, cmd->name); + if (attnum == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + cmd->name, RelationGetRelationName(rel)))); + + seq_relid = getIdentitySequence(relid, attnum, true); + + if (seq_relid) + { + AlterSeqStmt *seqstmt; + + seqstmt = makeNode(AlterSeqStmt); + seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)), + get_rel_name(seq_relid), -1); + seqstmt->options = newseqopts; + seqstmt->for_identity = true; + seqstmt->missing_ok = false; + + cxt.blist = lappend(cxt.blist, seqstmt); + } + + /* + * If column was not an identity column, we just let the + * ALTER TABLE command error out later. (There are cases + * this fails to cover, but we'll need to restructure + * where creation of the sequence dependency linkage + * happens before we can fix it.) + */ + + cmd->def = (Node *) newdef; + newcmds = lappend(newcmds, cmd); + break; + } + + case AT_AttachPartition: + case AT_DetachPartition: + { + PartitionCmd *partcmd = (PartitionCmd *) cmd->def; + + transformPartitionCmd(&cxt, partcmd); + /* assign transformed value of the partition bound */ + partcmd->bound = cxt.partbound; + } + + newcmds = lappend(newcmds, cmd); + break; + + default: + + /* + * Currently, we shouldn't actually get here for subcommand + * types that don't require transformation; but if we do, just + * emit them unchanged. + */ + newcmds = lappend(newcmds, cmd); + break; + } + } + + /* + * Transfer anything we already have in cxt.alist into save_alist, to keep + * it separate from the output of transformIndexConstraints. + */ + save_alist = cxt.alist; + cxt.alist = NIL; + + /* Postprocess constraints */ + transformIndexConstraints(&cxt); + transformFKConstraints(&cxt, skipValidation, true); + transformCheckConstraints(&cxt, false); + + /* + * 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 or AT_AddIndexConstraint + * subcommand has already been through transformIndexStmt. + */ + foreach(l, cxt.alist) + { + Node *istmt = (Node *) lfirst(l); + + /* + * We assume here that cxt.alist contains only IndexStmts and possibly + * ALTER TABLE SET NOT NULL statements generated from primary key + * constraints. We absorb the subcommands of the latter directly. + */ + if (IsA(istmt, IndexStmt)) + { + IndexStmt *idxstmt = (IndexStmt *) istmt; + + idxstmt = transformIndexStmt(relid, idxstmt, queryString); + newcmd = makeNode(AlterTableCmd); + newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex; + newcmd->def = (Node *) idxstmt; + newcmds = lappend(newcmds, newcmd); + } + else if (IsA(istmt, AlterTableStmt)) + { + AlterTableStmt *alterstmt = (AlterTableStmt *) istmt; + + newcmds = list_concat(newcmds, alterstmt->cmds); + } + else + elog(ERROR, "unexpected stmt type %d", (int) nodeTag(istmt)); + } + 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); + } + + /* Append extended statistics objects */ + transformExtendedStatistics(&cxt); + + /* Close rel */ + relation_close(rel, NoLock); + + /* + * Output results. + */ + stmt->cmds = newcmds; + + *beforeStmts = cxt.blist; + *afterStmts = list_concat(cxt.alist, save_alist); + + return stmt; +} + + +/* + * 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, UNIQUE, + * EXCLUSION, and PRIMARY KEY constraints, but someday they ought to be + * supported for other constraint types. + */ +static void +transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList) +{ + Constraint *lastprimarycon = NULL; + bool saw_deferrability = false; + bool saw_initially = false; + ListCell *clist; + +#define SUPPORTS_ATTRS(node) \ + ((node) != NULL && \ + ((node)->contype == CONSTR_PRIMARY || \ + (node)->contype == CONSTR_UNIQUE || \ + (node)->contype == CONSTR_EXCLUSION || \ + (node)->contype == CONSTR_FOREIGN)) + + foreach(clist, constraintList) + { + Constraint *con = (Constraint *) lfirst(clist); + + if (!IsA(con, Constraint)) + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(con)); + switch (con->contype) + { + case CONSTR_ATTR_DEFERRABLE: + if (!SUPPORTS_ATTRS(lastprimarycon)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("misplaced DEFERRABLE clause"), + parser_errposition(cxt->pstate, con->location))); + if (saw_deferrability) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed"), + parser_errposition(cxt->pstate, con->location))); + saw_deferrability = true; + lastprimarycon->deferrable = true; + break; + + case CONSTR_ATTR_NOT_DEFERRABLE: + if (!SUPPORTS_ATTRS(lastprimarycon)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("misplaced NOT DEFERRABLE clause"), + parser_errposition(cxt->pstate, con->location))); + if (saw_deferrability) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed"), + parser_errposition(cxt->pstate, con->location))); + saw_deferrability = true; + lastprimarycon->deferrable = false; + if (saw_initially && + lastprimarycon->initdeferred) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"), + parser_errposition(cxt->pstate, con->location))); + break; + + case CONSTR_ATTR_DEFERRED: + if (!SUPPORTS_ATTRS(lastprimarycon)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("misplaced INITIALLY DEFERRED clause"), + parser_errposition(cxt->pstate, con->location))); + if (saw_initially) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed"), + parser_errposition(cxt->pstate, con->location))); + saw_initially = true; + lastprimarycon->initdeferred = true; + + /* + * If only INITIALLY DEFERRED appears, assume DEFERRABLE + */ + if (!saw_deferrability) + lastprimarycon->deferrable = true; + else if (!lastprimarycon->deferrable) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"), + parser_errposition(cxt->pstate, con->location))); + break; + + case CONSTR_ATTR_IMMEDIATE: + if (!SUPPORTS_ATTRS(lastprimarycon)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("misplaced INITIALLY IMMEDIATE clause"), + parser_errposition(cxt->pstate, con->location))); + if (saw_initially) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed"), + parser_errposition(cxt->pstate, con->location))); + saw_initially = true; + lastprimarycon->initdeferred = false; + break; + + default: + /* Otherwise it's not an attribute */ + lastprimarycon = con; + /* reset flags for new primary node */ + saw_deferrability = false; + saw_initially = false; + break; + } + } +} + +/* + * Special handling of type definition for a column + */ +static void +transformColumnType(CreateStmtContext *cxt, ColumnDef *column) +{ + /* + * All we really need to do here is verify that the type is valid, + * including any collation spec that might be present. + */ + Type ctype = typenameType(cxt->pstate, column->typeName, NULL); + + if (column->collClause) + { + Form_pg_type typtup = (Form_pg_type) GETSTRUCT(ctype); + + LookupCollation(cxt->pstate, + column->collClause->collname, + column->collClause->location); + /* Complain if COLLATE is applied to an uncollatable type */ + if (!OidIsValid(typtup->typcollation)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("collations are not supported by type %s", + format_type_be(typtup->oid)), + parser_errposition(cxt->pstate, + column->collClause->location))); + } + + 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. + * + * SQL 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.authrole = (RoleSpec *) stmt->authrole; + 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))); +} + +/* + * transformPartitionCmd + * Analyze the ATTACH/DETACH PARTITION command + * + * In case of the ATTACH PARTITION command, cxt->partbound is set to the + * transformed value of cmd->bound. + */ +static void +transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd) +{ + Relation parentRel = cxt->rel; + + switch (parentRel->rd_rel->relkind) + { + case RELKIND_PARTITIONED_TABLE: + /* transform the partition bound, if any */ + Assert(RelationGetPartitionKey(parentRel) != NULL); + if (cmd->bound != NULL) + cxt->partbound = transformPartitionBound(cxt->pstate, parentRel, + cmd->bound); + break; + case RELKIND_PARTITIONED_INDEX: + + /* + * A partitioned index cannot have a partition bound set. ALTER + * INDEX prevents that with its grammar, but not ALTER TABLE. + */ + if (cmd->bound != NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("\"%s\" is not a partitioned table", + RelationGetRelationName(parentRel)))); + break; + case RELKIND_RELATION: + /* the table must be partitioned */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("table \"%s\" is not partitioned", + RelationGetRelationName(parentRel)))); + break; + case RELKIND_INDEX: + /* the index must be partitioned */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("index \"%s\" is not partitioned", + RelationGetRelationName(parentRel)))); + break; + default: + /* parser shouldn't let this case through */ + elog(ERROR, "\"%s\" is not a partitioned table or index", + RelationGetRelationName(parentRel)); + break; + } +} + +/* + * transformPartitionBound + * + * Transform a partition bound specification + */ +PartitionBoundSpec * +transformPartitionBound(ParseState *pstate, Relation parent, + PartitionBoundSpec *spec) +{ + PartitionBoundSpec *result_spec; + PartitionKey key = RelationGetPartitionKey(parent); + char strategy = get_partition_strategy(key); + int partnatts = get_partition_natts(key); + List *partexprs = get_partition_exprs(key); + + /* Avoid scribbling on input */ + result_spec = copyObject(spec); + + if (spec->is_default) + { + /* + * Hash partitioning does not support a default partition; there's no + * use case for it (since the set of partitions to create is perfectly + * defined), and if users do get into it accidentally, it's hard to + * back out from it afterwards. + */ + if (strategy == PARTITION_STRATEGY_HASH) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("a hash-partitioned table may not have a default partition"))); + + /* + * In case of the default partition, parser had no way to identify the + * partition strategy. Assign the parent's strategy to the default + * partition bound spec. + */ + result_spec->strategy = strategy; + + return result_spec; + } + + if (strategy == PARTITION_STRATEGY_HASH) + { + if (spec->strategy != PARTITION_STRATEGY_HASH) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("invalid bound specification for a hash partition"), + parser_errposition(pstate, exprLocation((Node *) spec)))); + + if (spec->modulus <= 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("modulus for hash partition must be an integer value greater than zero"))); + + Assert(spec->remainder >= 0); + + if (spec->remainder >= spec->modulus) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("remainder for hash partition must be less than modulus"))); + } + else if (strategy == PARTITION_STRATEGY_LIST) + { + ListCell *cell; + char *colname; + Oid coltype; + int32 coltypmod; + Oid partcollation; + + if (spec->strategy != PARTITION_STRATEGY_LIST) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("invalid bound specification for a list partition"), + parser_errposition(pstate, exprLocation((Node *) spec)))); + + /* Get the only column's name in case we need to output an error */ + if (key->partattrs[0] != 0) + colname = get_attname(RelationGetRelid(parent), + key->partattrs[0], false); + else + colname = deparse_expression((Node *) linitial(partexprs), + deparse_context_for(RelationGetRelationName(parent), + RelationGetRelid(parent)), + false, false); + /* Need its type data too */ + coltype = get_partition_col_typid(key, 0); + coltypmod = get_partition_col_typmod(key, 0); + partcollation = get_partition_col_collation(key, 0); + + result_spec->listdatums = NIL; + foreach(cell, spec->listdatums) + { + Node *expr = lfirst(cell); + Const *value; + ListCell *cell2; + bool duplicate; + + value = transformPartitionBoundValue(pstate, expr, + colname, coltype, coltypmod, + partcollation); + + /* Don't add to the result if the value is a duplicate */ + duplicate = false; + foreach(cell2, result_spec->listdatums) + { + Const *value2 = castNode(Const, lfirst(cell2)); + + if (equal(value, value2)) + { + duplicate = true; + break; + } + } + if (duplicate) + continue; + + result_spec->listdatums = lappend(result_spec->listdatums, + value); + } + } + else if (strategy == PARTITION_STRATEGY_RANGE) + { + if (spec->strategy != PARTITION_STRATEGY_RANGE) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("invalid bound specification for a range partition"), + parser_errposition(pstate, exprLocation((Node *) spec)))); + + if (list_length(spec->lowerdatums) != partnatts) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("FROM must specify exactly one value per partitioning column"))); + if (list_length(spec->upperdatums) != partnatts) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("TO must specify exactly one value per partitioning column"))); + + /* + * Convert raw parse nodes into PartitionRangeDatum nodes and perform + * any necessary validation. + */ + result_spec->lowerdatums = + transformPartitionRangeBounds(pstate, spec->lowerdatums, + parent); + result_spec->upperdatums = + transformPartitionRangeBounds(pstate, spec->upperdatums, + parent); + } + else + elog(ERROR, "unexpected partition strategy: %d", (int) strategy); + + return result_spec; +} + +/* + * transformPartitionRangeBounds + * This converts the expressions for range partition bounds from the raw + * grammar representation to PartitionRangeDatum structs + */ +static List * +transformPartitionRangeBounds(ParseState *pstate, List *blist, + Relation parent) +{ + List *result = NIL; + PartitionKey key = RelationGetPartitionKey(parent); + List *partexprs = get_partition_exprs(key); + ListCell *lc; + int i, + j; + + i = j = 0; + foreach(lc, blist) + { + Node *expr = lfirst(lc); + PartitionRangeDatum *prd = NULL; + + /* + * Infinite range bounds -- "minvalue" and "maxvalue" -- get passed in + * as ColumnRefs. + */ + if (IsA(expr, ColumnRef)) + { + ColumnRef *cref = (ColumnRef *) expr; + char *cname = NULL; + + /* + * There should be a single field named either "minvalue" or + * "maxvalue". + */ + if (list_length(cref->fields) == 1 && + IsA(linitial(cref->fields), String)) + cname = strVal(linitial(cref->fields)); + + if (cname == NULL) + { + /* + * ColumnRef is not in the desired single-field-name form. For + * consistency between all partition strategies, let the + * expression transformation report any errors rather than + * doing it ourselves. + */ + } + else if (strcmp("minvalue", cname) == 0) + { + prd = makeNode(PartitionRangeDatum); + prd->kind = PARTITION_RANGE_DATUM_MINVALUE; + prd->value = NULL; + } + else if (strcmp("maxvalue", cname) == 0) + { + prd = makeNode(PartitionRangeDatum); + prd->kind = PARTITION_RANGE_DATUM_MAXVALUE; + prd->value = NULL; + } + } + + if (prd == NULL) + { + char *colname; + Oid coltype; + int32 coltypmod; + Oid partcollation; + Const *value; + + /* Get the column's name in case we need to output an error */ + if (key->partattrs[i] != 0) + colname = get_attname(RelationGetRelid(parent), + key->partattrs[i], false); + else + { + colname = deparse_expression((Node *) list_nth(partexprs, j), + deparse_context_for(RelationGetRelationName(parent), + RelationGetRelid(parent)), + false, false); + ++j; + } + + /* Need its type data too */ + coltype = get_partition_col_typid(key, i); + coltypmod = get_partition_col_typmod(key, i); + partcollation = get_partition_col_collation(key, i); + + value = transformPartitionBoundValue(pstate, expr, + colname, + coltype, coltypmod, + partcollation); + if (value->constisnull) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot specify NULL in range bound"))); + prd = makeNode(PartitionRangeDatum); + prd->kind = PARTITION_RANGE_DATUM_VALUE; + prd->value = (Node *) value; + ++i; + } + + prd->location = exprLocation(expr); + + result = lappend(result, prd); + } + + /* + * Once we see MINVALUE or MAXVALUE for one column, the remaining columns + * must be the same. + */ + validateInfiniteBounds(pstate, result); + + return result; +} + +/* + * validateInfiniteBounds + * + * Check that a MAXVALUE or MINVALUE specification in a partition bound is + * followed only by more of the same. + */ +static void +validateInfiniteBounds(ParseState *pstate, List *blist) +{ + ListCell *lc; + PartitionRangeDatumKind kind = PARTITION_RANGE_DATUM_VALUE; + + foreach(lc, blist) + { + PartitionRangeDatum *prd = castNode(PartitionRangeDatum, lfirst(lc)); + + if (kind == prd->kind) + continue; + + switch (kind) + { + case PARTITION_RANGE_DATUM_VALUE: + kind = prd->kind; + break; + + case PARTITION_RANGE_DATUM_MAXVALUE: + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("every bound following MAXVALUE must also be MAXVALUE"), + parser_errposition(pstate, exprLocation((Node *) prd)))); + break; + + case PARTITION_RANGE_DATUM_MINVALUE: + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("every bound following MINVALUE must also be MINVALUE"), + parser_errposition(pstate, exprLocation((Node *) prd)))); + break; + } + } +} + +/* + * Transform one entry in a partition bound spec, producing a constant. + */ +static Const * +transformPartitionBoundValue(ParseState *pstate, Node *val, + const char *colName, Oid colType, int32 colTypmod, + Oid partCollation) +{ + Node *value; + + /* Transform raw parsetree */ + value = transformExpr(pstate, val, EXPR_KIND_PARTITION_BOUND); + + /* + * transformExpr() should have already rejected column references, + * subqueries, aggregates, window functions, and SRFs, based on the + * EXPR_KIND_ of a partition bound expression. + */ + Assert(!contain_var_clause(value)); + + /* + * Coerce to the correct type. This might cause an explicit coercion step + * to be added on top of the expression, which must be evaluated before + * returning the result to the caller. + */ + value = coerce_to_target_type(pstate, + value, exprType(value), + colType, + colTypmod, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + + if (value == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("specified value cannot be cast to type %s for column \"%s\"", + format_type_be(colType), colName), + parser_errposition(pstate, exprLocation(val)))); + + /* + * Evaluate the expression, if needed, assigning the partition key's data + * type and collation to the resulting Const node. + */ + if (!IsA(value, Const)) + { + assign_expr_collations(pstate, value); + value = (Node *) expression_planner((Expr *) value); + value = (Node *) evaluate_expr((Expr *) value, colType, colTypmod, + partCollation); + if (!IsA(value, Const)) + elog(ERROR, "could not evaluate partition bound expression"); + } + else + { + /* + * If the expression is already a Const, as is often the case, we can + * skip the rather expensive steps above. But we still have to insert + * the right collation, since coerce_to_target_type doesn't handle + * that. + */ + ((Const *) value)->constcollid = partCollation; + } + + /* + * Attach original expression's parse location to the Const, so that + * that's what will be reported for any later errors related to this + * partition bound. + */ + ((Const *) value)->location = exprLocation(val); + + return (Const *) value; +} |