From faa1afc6c16631424579548a6e2fafb130f834f4 Mon Sep 17 00:00:00 2001 From: Andrew Dunstan Date: Mon, 12 Oct 2009 19:49:24 +0000 Subject: [PATCH] CREATE LIKE INCLUDING COMMENTS and STORAGE, and INCLUDING ALL shortcut. Itagaki Takahiro. --- doc/src/sgml/ref/create_table.sgml | 27 +++- src/backend/access/common/tupdesc.c | 4 +- src/backend/catalog/pg_constraint.c | 64 +++++++++- src/backend/commands/comment.c | 105 +++++++++------- src/backend/commands/tablecmds.c | 63 +++++++++- src/backend/nodes/copyfuncs.c | 4 +- src/backend/nodes/equalfuncs.c | 4 +- src/backend/parser/gram.y | 25 ++-- src/backend/parser/parse_utilcmd.c | 173 ++++++++++++++++++++------ src/include/catalog/pg_constraint.h | 3 +- src/include/commands/comment.h | 4 +- src/include/nodes/parsenodes.h | 17 +-- src/include/parser/kwlist.h | 3 +- src/test/regress/expected/inherit.out | 136 ++++++++++++++++++++ src/test/regress/sql/inherit.sql | 49 ++++++++ 15 files changed, 564 insertions(+), 117 deletions(-) diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index dc9b6855d9..a63333cb6f 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -1,5 +1,5 @@ @@ -24,7 +24,7 @@ PostgreSQL documentation CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE table_name ( [ { column_name data_type [ DEFAULT default_expr ] [ column_constraint [ ... ] ] | table_constraint - | LIKE parent_table [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES } ] ... } + | LIKE parent_table [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL } ] ... } [, ... ] ] ) [ INHERITS ( parent_table [, ... ] ) ] @@ -230,6 +230,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE - LIKE parent_table [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES } ] + LIKE parent_table [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL } ] The LIKE clause specifies a table from which @@ -280,6 +284,23 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE . + + + Comments for the copied column, constraint, index and columns of index + definitions will only be copied if INCLUDING COMMENTS + is specified. The default behavior is to exclude comments, resulting in + the copied columns and constraints in the new table having no comments. + + + INCLUDING ALL is an abbreviated form of + INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS. + Note also that unlike INHERITS, copied columns and constraints are not merged with similarly named columns and constraints. diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index 9c561beed1..f19fc9bc95 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/common/tupdesc.c,v 1.128 2009/08/02 22:14:51 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/access/common/tupdesc.c,v 1.129 2009/10/12 19:49:24 adunstan Exp $ * * NOTES * some of the executor utility code such as "ExecTypeFromTL" should be @@ -558,6 +558,8 @@ BuildDescForRelation(List *schema) has_not_null |= entry->is_not_null; desc->attrs[attnum - 1]->attislocal = entry->is_local; desc->attrs[attnum - 1]->attinhcount = entry->inhcount; + if (entry->storage) + desc->attrs[attnum - 1]->attstorage = entry->storage; } if (has_not_null) diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 81c35100c7..9aa13517ea 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/pg_constraint.c,v 1.47 2009/07/28 02:56:29 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/pg_constraint.c,v 1.48 2009/10/12 19:49:24 adunstan Exp $ * *------------------------------------------------------------------------- */ @@ -702,3 +702,65 @@ AlterConstraintNamespaces(Oid ownerId, Oid oldNspId, heap_close(conRel, RowExclusiveLock); } + +/* + * GetConstraintByName + * Find a constraint with the specified name. + */ +Oid +GetConstraintByName(Oid relid, const char *conname) +{ + Relation pg_constraint; + HeapTuple tuple; + SysScanDesc scan; + ScanKeyData skey[1]; + Oid conOid = InvalidOid; + + /* + * Fetch the constraint tuple from pg_constraint. There may be more than + * one match, because constraints are not required to have unique names; + * if so, error out. + */ + pg_constraint = heap_open(ConstraintRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + Anum_pg_constraint_conrelid, + BTEqualStrategyNumber, F_OIDEQ, relid); + + scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true, + SnapshotNow, 1, skey); + + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple); + + if (strcmp(NameStr(con->conname), conname) == 0) + { + if (OidIsValid(conOid)) + { + char *relname = get_rel_name(relid); + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("table \"%s\" has multiple constraints named \"%s\"", + (relname ? relname : "(unknown)"), conname))); + } + conOid = HeapTupleGetOid(tuple); + } + } + + systable_endscan(scan); + + /* If no constraint exists for the relation specified, notify user */ + if (!OidIsValid(conOid)) + { + char *relname = get_rel_name(relid); + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("constraint \"%s\" for table \"%s\" does not exist", + conname, (relname ? relname : "(unknown)")))); + } + + heap_close(pg_constraint, AccessShareLock); + + return conOid; +} diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c index b6a90a248b..610816db6d 100644 --- a/src/backend/commands/comment.c +++ b/src/backend/commands/comment.c @@ -7,7 +7,7 @@ * Copyright (c) 1996-2009, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/comment.c,v 1.107 2009/06/11 14:48:55 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/comment.c,v 1.108 2009/10/12 19:49:24 adunstan Exp $ * *------------------------------------------------------------------------- */ @@ -462,6 +462,61 @@ DeleteSharedComments(Oid oid, Oid classoid) heap_close(shdescription, RowExclusiveLock); } +/* + * GetComment -- get the comment for an object, or null if not found. + */ +char * +GetComment(Oid oid, Oid classoid, int32 subid) +{ + Relation description; + ScanKeyData skey[3]; + SysScanDesc sd; + TupleDesc tupdesc; + HeapTuple tuple; + char *comment; + + /* Use the index to search for a matching old tuple */ + + ScanKeyInit(&skey[0], + Anum_pg_description_objoid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(oid)); + ScanKeyInit(&skey[1], + Anum_pg_description_classoid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(classoid)); + ScanKeyInit(&skey[2], + Anum_pg_description_objsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(subid)); + + description = heap_open(DescriptionRelationId, AccessShareLock); + tupdesc = RelationGetDescr(description); + + sd = systable_beginscan(description, DescriptionObjIndexId, true, + SnapshotNow, 3, skey); + + comment = NULL; + while ((tuple = systable_getnext(sd)) != NULL) + { + Datum value; + bool isnull; + + /* Found the tuple, get description field */ + value = heap_getattr(tuple, Anum_pg_description_description, tupdesc, &isnull); + if (!isnull) + comment = TextDatumGetCString(value); + break; /* Assume there can be only one match */ + } + + systable_endscan(sd); + + /* Done */ + heap_close(description, AccessShareLock); + + return comment; +} + /* * CommentRelation -- * @@ -1064,12 +1119,8 @@ CommentConstraint(List *qualname, char *comment) List *relName; char *conName; RangeVar *rel; - Relation pg_constraint, - relation; - HeapTuple tuple; - SysScanDesc scan; - ScanKeyData skey[1]; - Oid conOid = InvalidOid; + Relation relation; + Oid conOid; /* Separate relname and constraint name */ nnames = list_length(qualname); @@ -1088,50 +1139,12 @@ CommentConstraint(List *qualname, char *comment) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(relation)); - /* - * Fetch the constraint tuple from pg_constraint. There may be more than - * one match, because constraints are not required to have unique names; - * if so, error out. - */ - pg_constraint = heap_open(ConstraintRelationId, AccessShareLock); - - ScanKeyInit(&skey[0], - Anum_pg_constraint_conrelid, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(RelationGetRelid(relation))); - - scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true, - SnapshotNow, 1, skey); - - while (HeapTupleIsValid(tuple = systable_getnext(scan))) - { - Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple); - - if (strcmp(NameStr(con->conname), conName) == 0) - { - if (OidIsValid(conOid)) - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_OBJECT), - errmsg("table \"%s\" has multiple constraints named \"%s\"", - RelationGetRelationName(relation), conName))); - conOid = HeapTupleGetOid(tuple); - } - } - - systable_endscan(scan); - - /* If no constraint exists for the relation specified, notify user */ - if (!OidIsValid(conOid)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("constraint \"%s\" for table \"%s\" does not exist", - conName, RelationGetRelationName(relation)))); + conOid = GetConstraintByName(RelationGetRelid(relation), conName); /* Call CreateComments() to create/drop the comments */ CreateComments(conOid, ConstraintRelationId, 0, comment); /* Done, but hold lock on relation */ - heap_close(pg_constraint, AccessShareLock); heap_close(relation, NoLock); } diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 2d728991b6..a0d3f41886 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.301 2009/10/06 00:55:26 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.302 2009/10/12 19:49:24 adunstan Exp $ * *------------------------------------------------------------------------- */ @@ -39,6 +39,7 @@ #include "catalog/storage.h" #include "catalog/toasting.h" #include "commands/cluster.h" +#include "commands/comment.h" #include "commands/defrem.h" #include "commands/sequence.h" #include "commands/tablecmds.h" @@ -332,6 +333,7 @@ static void ATExecAddInherit(Relation rel, RangeVar *parent); static void ATExecDropInherit(Relation rel, RangeVar *parent); static void copy_relation_data(SMgrRelation rel, SMgrRelation dst, ForkNumber forkNum, bool istemp); +static const char * storage_name(char c); /* ---------------------------------------------------------------- @@ -1100,6 +1102,25 @@ truncate_check_rel(Relation rel) CheckTableNotInUse(rel, "TRUNCATE"); } + +/*---------------- + * storage_name + * returns a name corresponding to a storage enum value + * For use in error messages + */ +static const char * +storage_name(char c) +{ + switch (c) + { + case 'p': return "PLAIN"; + case 'm': return "MAIN"; + case 'x': return "EXTENDED"; + case 'e': return "EXTERNAL"; + default: return "???"; + } +} + /*---------- * MergeAttributes * Returns new schema given initial schema and superclasses. @@ -1168,6 +1189,7 @@ MergeAttributes(List *schema, List *supers, bool istemp, List *constraints = NIL; int parentsWithOids = 0; bool have_bogus_defaults = false; + bool have_bogus_comments = false; int child_attno; static Node bogus_marker = { 0 }; /* marks conflicting defaults */ @@ -1323,6 +1345,18 @@ MergeAttributes(List *schema, List *supers, bool istemp, errdetail("%s versus %s", TypeNameToString(def->typeName), format_type_be(attribute->atttypid)))); + + /* Copy storage parameter */ + if (def->storage == 0) + def->storage = attribute->attstorage; + else if (def->storage != attribute->attstorage) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("inherited column \"%s\" has a storage parameter conflict", + attributeName), + errdetail("%s versus %s", storage_name(def->storage), + storage_name(attribute->attstorage)))); + def->inhcount++; /* Merge of NOT NULL constraints = OR 'em together */ def->is_not_null |= attribute->attnotnull; @@ -1344,6 +1378,7 @@ MergeAttributes(List *schema, List *supers, bool istemp, def->raw_default = NULL; def->cooked_default = NULL; def->constraints = NIL; + def->storage = attribute->attstorage; inhSchema = lappend(inhSchema, def); newattno[parent_attno - 1] = ++child_attno; } @@ -1481,6 +1516,18 @@ MergeAttributes(List *schema, List *supers, bool istemp, errdetail("%s versus %s", TypeNameToString(def->typeName), TypeNameToString(newdef->typeName)))); + + /* Copy storage parameter */ + if (def->storage == 0) + def->storage = newdef->storage; + else if (newdef->storage != 0 && def->storage != newdef->storage) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("column \"%s\" has a storage parameter conflict", + attributeName), + errdetail("%s versus %s", storage_name(def->storage), + storage_name(newdef->storage)))); + /* Mark the column as locally defined */ def->is_local = true; /* Merge of NOT NULL constraints = OR 'em together */ @@ -1533,6 +1580,20 @@ MergeAttributes(List *schema, List *supers, bool istemp, } } + /* Raise an error if we found conflicting comments. */ + if (have_bogus_comments) + { + foreach(entry, schema) + { + ColumnDef *def = lfirst(entry); + + if (def->cooked_default == &bogus_marker) + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), + errmsg("column \"%s\" inherits conflicting comments", def->colname))); + } + } + *supOids = parentOids; *supconstr = constraints; *supOidCount = parentsWithOids; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index eef333b718..8365586a00 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.444 2009/10/12 18:10:45 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.445 2009/10/12 19:49:24 adunstan Exp $ * *------------------------------------------------------------------------- */ @@ -2489,7 +2489,7 @@ _copyInhRelation(InhRelation *from) InhRelation *newnode = makeNode(InhRelation); COPY_NODE_FIELD(relation); - COPY_NODE_FIELD(options); + COPY_SCALAR_FIELD(options); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index ef8abc4773..2c916af1b7 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -22,7 +22,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.366 2009/10/12 18:10:45 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.367 2009/10/12 19:49:24 adunstan Exp $ * *------------------------------------------------------------------------- */ @@ -1107,7 +1107,7 @@ static bool _equalInhRelation(InhRelation *a, InhRelation *b) { COMPARE_NODE_FIELD(relation); - COMPARE_NODE_FIELD(options); + COMPARE_SCALAR_FIELD(options); return true; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index b86df8a870..50ce33070f 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.682 2009/10/08 02:39:22 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.683 2009/10/12 19:49:24 adunstan Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -407,8 +407,7 @@ static TypeName *TableFuncTypeName(List *columns); %type col_name_keyword reserved_keyword %type TableConstraint TableLikeClause -%type TableLikeOptionList -%type TableLikeOption +%type TableLikeOptionList TableLikeOption %type ColQualList %type ColConstraint ColConstraintElem ConstraintAttr %type key_actions key_delete key_match key_update key_action @@ -466,7 +465,7 @@ static TypeName *TableFuncTypeName(List *columns); CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE - CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT + CLUSTER COALESCE COLLATE COLUMN COMMENT COMMENTS COMMIT COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB CREATEROLE CREATEUSER CROSS CSV CURRENT_P @@ -2417,17 +2416,18 @@ TableLikeClause: ; TableLikeOptionList: - TableLikeOptionList TableLikeOption { $$ = lappend_int($1, $2); } - | /* EMPTY */ { $$ = NIL; } + TableLikeOptionList INCLUDING TableLikeOption { $$ = $1 | $3; } + | TableLikeOptionList EXCLUDING TableLikeOption { $$ = $1 & ~$3; } + | /* EMPTY */ { $$ = 0; } ; TableLikeOption: - INCLUDING DEFAULTS { $$ = CREATE_TABLE_LIKE_INCLUDING_DEFAULTS; } - | EXCLUDING DEFAULTS { $$ = CREATE_TABLE_LIKE_EXCLUDING_DEFAULTS; } - | INCLUDING CONSTRAINTS { $$ = CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS; } - | EXCLUDING CONSTRAINTS { $$ = CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS; } - | INCLUDING INDEXES { $$ = CREATE_TABLE_LIKE_INCLUDING_INDEXES; } - | EXCLUDING INDEXES { $$ = CREATE_TABLE_LIKE_EXCLUDING_INDEXES; } + DEFAULTS { $$ = CREATE_TABLE_LIKE_DEFAULTS; } + | CONSTRAINTS { $$ = CREATE_TABLE_LIKE_CONSTRAINTS; } + | INDEXES { $$ = CREATE_TABLE_LIKE_INDEXES; } + | STORAGE { $$ = CREATE_TABLE_LIKE_STORAGE; } + | COMMENTS { $$ = CREATE_TABLE_LIKE_COMMENTS; } + | ALL { $$ = CREATE_TABLE_LIKE_ALL; } ; @@ -10481,6 +10481,7 @@ unreserved_keyword: | CLOSE | CLUSTER | COMMENT + | COMMENTS | COMMIT | COMMITTED | CONCURRENTLY diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index f7a122c0d6..7dfdd28601 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -19,7 +19,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.26 2009/10/06 00:55:26 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.27 2009/10/12 19:49:24 adunstan Exp $ * *------------------------------------------------------------------------- */ @@ -36,6 +36,7 @@ #include "catalog/pg_constraint.h" #include "catalog/pg_opclass.h" #include "catalog/pg_type.h" +#include "commands/comment.h" #include "commands/defrem.h" #include "commands/tablecmds.h" #include "commands/tablespace.h" @@ -101,6 +102,7 @@ static void transformTableConstraint(ParseState *pstate, Constraint *constraint); static void transformInhRelation(ParseState *pstate, CreateStmtContext *cxt, InhRelation *inhrelation); +static char *chooseIndexName(const RangeVar *relation, IndexStmt *index_stmt); static IndexStmt *generateClonedIndexStmt(CreateStmtContext *cxt, Relation parent_index, AttrNumber *attmap); static List *get_opclass(Oid opclass, Oid actual_datatype); @@ -546,10 +548,7 @@ transformInhRelation(ParseState *pstate, CreateStmtContext *cxt, TupleDesc tupleDesc; TupleConstr *constr; AclResult aclresult; - bool including_defaults = false; - bool including_constraints = false; - bool including_indexes = false; - ListCell *elem; + char *comment; relation = parserOpenTable(pstate, inhRelation->relation, AccessShareLock); @@ -571,36 +570,6 @@ transformInhRelation(ParseState *pstate, CreateStmtContext *cxt, tupleDesc = RelationGetDescr(relation); constr = tupleDesc->constr; - foreach(elem, inhRelation->options) - { - int option = lfirst_int(elem); - - switch (option) - { - case CREATE_TABLE_LIKE_INCLUDING_DEFAULTS: - including_defaults = true; - break; - case CREATE_TABLE_LIKE_EXCLUDING_DEFAULTS: - including_defaults = false; - break; - case CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS: - including_constraints = true; - break; - case CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS: - including_constraints = false; - break; - case CREATE_TABLE_LIKE_INCLUDING_INDEXES: - including_indexes = true; - break; - case CREATE_TABLE_LIKE_EXCLUDING_INDEXES: - including_indexes = false; - break; - default: - elog(ERROR, "unrecognized CREATE TABLE LIKE option: %d", - option); - } - } - /* * Insert the copied attributes into the cxt for the new table definition. */ @@ -642,7 +611,8 @@ transformInhRelation(ParseState *pstate, CreateStmtContext *cxt, /* * Copy default, if present and the default has been requested */ - if (attribute->atthasdef && including_defaults) + if (attribute->atthasdef && + (inhRelation->options & CREATE_TABLE_LIKE_DEFAULTS)) { Node *this_default = NULL; AttrDefault *attrdef; @@ -668,13 +638,34 @@ transformInhRelation(ParseState *pstate, CreateStmtContext *cxt, def->cooked_default = this_default; } + + /* Likewise, copy storage if requested */ + if (inhRelation->options & CREATE_TABLE_LIKE_STORAGE) + def->storage = attribute->attstorage; + + /* Likewise, copy comment if requested */ + if ((inhRelation->options & CREATE_TABLE_LIKE_COMMENTS) && + (comment = GetComment(attribute->attrelid, RelationRelationId, + attribute->attnum)) != NULL) + { + CommentStmt *stmt = makeNode(CommentStmt); + + stmt->objtype = OBJECT_COLUMN; + stmt->objname = list_make3(makeString(cxt->relation->schemaname), + makeString(cxt->relation->relname), + makeString(def->colname)); + stmt->objargs = NIL; + stmt->comment = comment; + + cxt->alist = lappend(cxt->alist, stmt); + } } /* * Copy CHECK constraints if requested, being careful to adjust attribute * numbers */ - if (including_constraints && tupleDesc->constr) + if ((inhRelation->options & CREATE_TABLE_LIKE_CONSTRAINTS) && tupleDesc->constr) { AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns); int ccnum; @@ -694,13 +685,31 @@ transformInhRelation(ParseState *pstate, CreateStmtContext *cxt, n->raw_expr = NULL; n->cooked_expr = nodeToString(ccbin_node); cxt->ckconstraints = lappend(cxt->ckconstraints, n); + + /* Copy comment on constraint */ + if ((inhRelation->options & CREATE_TABLE_LIKE_COMMENTS) && + (comment = GetComment(GetConstraintByName(RelationGetRelid( + relation), n->conname), ConstraintRelationId, 0)) != NULL) + { + CommentStmt *stmt = makeNode(CommentStmt); + + stmt->objtype = OBJECT_CONSTRAINT; + stmt->objname = list_make3(makeString(cxt->relation->schemaname), + makeString(cxt->relation->relname), + makeString(n->conname)); + stmt->objargs = NIL; + stmt->comment = comment; + + cxt->alist = lappend(cxt->alist, stmt); + } } } /* * Likewise, copy indexes if requested */ - if (including_indexes && relation->rd_rel->relhasindex) + if ((inhRelation->options & CREATE_TABLE_LIKE_INDEXES) && + relation->rd_rel->relhasindex) { AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns); List *parent_indexes; @@ -719,6 +728,68 @@ transformInhRelation(ParseState *pstate, CreateStmtContext *cxt, /* Build CREATE INDEX statement to recreate the parent_index */ index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap); + /* Copy comment on index */ + if (inhRelation->options & CREATE_TABLE_LIKE_COMMENTS) + { + CommentStmt *stmt; + ListCell *lc; + int i; + + comment = GetComment(parent_index_oid, RelationRelationId, 0); + + if (comment != NULL) + { + /* Assign name for index because CommentStmt requires name. */ + if (index_stmt->idxname == NULL) + index_stmt->idxname = chooseIndexName(cxt->relation, index_stmt); + + stmt = makeNode(CommentStmt); + stmt->objtype = OBJECT_INDEX; + stmt->objname = list_make2(makeString(cxt->relation->schemaname), + makeString(index_stmt->idxname)); + stmt->objargs = NIL; + stmt->comment = comment; + + cxt->alist = lappend(cxt->alist, stmt); + } + + /* Copy comment on index's columns */ + i = 0; + foreach(lc, index_stmt->indexParams) + { + char *attname; + + i++; + comment = GetComment(parent_index_oid, RelationRelationId, i); + if (comment == NULL) + continue; + + /* Assign name for index because CommentStmt requires name. */ + if (index_stmt->idxname == NULL) + index_stmt->idxname = chooseIndexName(cxt->relation, index_stmt); + + attname = ((IndexElem *) lfirst(lc))->name; + + /* expression index has a dummy column name */ + if (attname == NULL) + { + attname = palloc(NAMEDATALEN); + sprintf(attname, "pg_expression_%d", i); + } + + stmt = makeNode(CommentStmt); + stmt->objtype = OBJECT_COLUMN; + stmt->objname = list_make3( + makeString(cxt->relation->schemaname), + makeString(index_stmt->idxname), + makeString(attname)); + stmt->objargs = NIL; + stmt->comment = comment; + + cxt->alist = lappend(cxt->alist, stmt); + } + } + /* Save it in the inh_indexes list for the time being */ cxt->inh_indexes = lappend(cxt->inh_indexes, index_stmt); @@ -734,6 +805,32 @@ transformInhRelation(ParseState *pstate, CreateStmtContext *cxt, heap_close(relation, NoLock); } +/* + * chooseIndexName + * + * Set name to unnamed index. See also the same logic in DefineIndex. + */ +static char * +chooseIndexName(const RangeVar *relation, IndexStmt *index_stmt) +{ + Oid namespaceId; + + namespaceId = RangeVarGetCreationNamespace(relation); + if (index_stmt->primary) + { + /* no need for column list with pkey */ + return ChooseRelationName(relation->relname, NULL, + "pkey", namespaceId); + } + else + { + IndexElem *iparam = (IndexElem *) linitial(index_stmt->indexParams); + + return ChooseRelationName(relation->relname, iparam->name, + "key", namespaceId); + } +} + /* * Generate an IndexStmt node using information from an already existing index * "source_idx". Attribute numbers should be adjusted according to attmap. diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h index 08305e68b6..9822fc8c08 100644 --- a/src/include/catalog/pg_constraint.h +++ b/src/include/catalog/pg_constraint.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_constraint.h,v 1.32 2009/07/28 02:56:31 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_constraint.h,v 1.33 2009/10/12 19:49:24 adunstan Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -226,5 +226,6 @@ extern char *ChooseConstraintName(const char *name1, const char *name2, extern void AlterConstraintNamespaces(Oid ownerId, Oid oldNspId, Oid newNspId, bool isType); +extern Oid GetConstraintByName(Oid relid, const char *conname); #endif /* PG_CONSTRAINT_H */ diff --git a/src/include/commands/comment.h b/src/include/commands/comment.h index f302bd6755..fe36ae9c24 100644 --- a/src/include/commands/comment.h +++ b/src/include/commands/comment.h @@ -1,5 +1,5 @@ /* - * $PostgreSQL: pgsql/src/include/commands/comment.h,v 1.24 2009/06/11 14:49:11 momjian Exp $ + * $PostgreSQL: pgsql/src/include/commands/comment.h,v 1.25 2009/10/12 19:49:24 adunstan Exp $ * *------------------------------------------------------------------------- * @@ -39,4 +39,6 @@ extern void DeleteSharedComments(Oid oid, Oid classoid); extern void CreateSharedComments(Oid oid, Oid classoid, char *comment); +extern char *GetComment(Oid oid, Oid classoid, int32 subid); + #endif /* COMMENT_H */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 757aebef1a..cf755724cc 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -13,7 +13,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.406 2009/10/12 18:10:51 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.407 2009/10/12 19:49:24 adunstan Exp $ * *------------------------------------------------------------------------- */ @@ -460,6 +460,7 @@ typedef struct ColumnDef int inhcount; /* number of times column is inherited */ bool is_local; /* column has local (non-inherited) def'n */ bool is_not_null; /* NOT NULL constraint specified? */ + char storage; /* storage parameter of column */ Node *raw_default; /* default value (untransformed parse tree) */ Node *cooked_default; /* default value (transformed expr tree) */ List *constraints; /* other constraints on column */ @@ -472,17 +473,17 @@ typedef struct InhRelation { NodeTag type; RangeVar *relation; - List *options; /* integer List of CreateStmtLikeOption */ + bits32 options; /* bitmap of CreateStmtLikeOption */ } InhRelation; typedef enum CreateStmtLikeOption { - CREATE_TABLE_LIKE_INCLUDING_DEFAULTS, - CREATE_TABLE_LIKE_EXCLUDING_DEFAULTS, - CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS, - CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS, - CREATE_TABLE_LIKE_INCLUDING_INDEXES, - CREATE_TABLE_LIKE_EXCLUDING_INDEXES + CREATE_TABLE_LIKE_DEFAULTS = 1 << 0, + CREATE_TABLE_LIKE_CONSTRAINTS = 1 << 1, + CREATE_TABLE_LIKE_INDEXES = 1 << 2, + CREATE_TABLE_LIKE_STORAGE = 1 << 3, + CREATE_TABLE_LIKE_COMMENTS = 1 << 4, + CREATE_TABLE_LIKE_ALL = 0xFFFFFFFF } CreateStmtLikeOption; /* diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index d1fd91795b..4b1284f338 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -11,7 +11,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/include/parser/kwlist.h,v 1.3 2009/09/22 23:43:41 tgl Exp $ + * $PostgreSQL: pgsql/src/include/parser/kwlist.h,v 1.4 2009/10/12 19:49:24 adunstan Exp $ * *------------------------------------------------------------------------- */ @@ -80,6 +80,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD) PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD) PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD) PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD) +PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD) PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD) PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD) PG_KEYWORD("concurrently", CONCURRENTLY, UNRESERVED_KEYWORD) diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index 69078ae50a..aebadd2c9c 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -921,3 +921,139 @@ drop table pp1 cascade; NOTICE: drop cascades to 2 other objects DETAIL: drop cascades to table cc1 drop cascades to table cc2 +-- including storage and comments +CREATE TABLE t1 (a text CHECK (length(a) > 2) PRIMARY KEY, b text); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t1_pkey" for table "t1" +CREATE INDEX t1_b_key ON t1 (b); +CREATE INDEX t1_fnidx ON t1 ((a || b)); +COMMENT ON COLUMN t1.a IS 'A'; +COMMENT ON COLUMN t1.b IS 'B'; +COMMENT ON CONSTRAINT t1_a_check ON t1 IS 't1_a_check'; +COMMENT ON INDEX t1_pkey IS 'index pkey'; +COMMENT ON INDEX t1_b_key IS 'index b_key'; +COMMENT ON COLUMN t1_pkey.a IS 'index column pkey.a'; +COMMENT ON COLUMN t1_fnidx.pg_expression_1 IS 'index column fnidx'; +ALTER TABLE t1 ALTER COLUMN a SET STORAGE MAIN; +CREATE TABLE t2 (c text); +ALTER TABLE t2 ALTER COLUMN c SET STORAGE EXTERNAL; +COMMENT ON COLUMN t2.c IS 'C'; +CREATE TABLE t3 (a text CHECK (length(a) < 5), c text); +ALTER TABLE t3 ALTER COLUMN c SET STORAGE EXTERNAL; +ALTER TABLE t3 ALTER COLUMN a SET STORAGE MAIN; +COMMENT ON COLUMN t3.a IS 'A3'; +COMMENT ON COLUMN t3.c IS 'C'; +COMMENT ON CONSTRAINT t3_a_check ON t3 IS 't3_a_check'; +CREATE TABLE t4 (a text, c text); +ALTER TABLE t4 ALTER COLUMN c SET STORAGE EXTERNAL; +CREATE TABLE t12_storage (LIKE t1 INCLUDING STORAGE, LIKE t2 INCLUDING STORAGE); +\d+ t12_storage + Table "public.t12_storage" + Column | Type | Modifiers | Storage | Description +--------+------+-----------+----------+------------- + a | text | not null | main | + b | text | | extended | + c | text | | external | +Has OIDs: no + +CREATE TABLE t12_comments (LIKE t1 INCLUDING COMMENTS, LIKE t2 INCLUDING COMMENTS); +\d+ t12_comments + Table "public.t12_comments" + Column | Type | Modifiers | Storage | Description +--------+------+-----------+----------+------------- + a | text | not null | extended | A + b | text | | extended | B + c | text | | extended | C +Has OIDs: no + +CREATE TABLE t1_inh (LIKE t1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (t1); +NOTICE: merging column "a" with inherited definition +NOTICE: merging column "b" with inherited definition +NOTICE: merging constraint "t1_a_check" with inherited definition +\d+ t1_inh + Table "public.t1_inh" + Column | Type | Modifiers | Storage | Description +--------+------+-----------+----------+------------- + a | text | not null | main | A + b | text | | extended | B +Check constraints: + "t1_a_check" CHECK (length(a) > 2) +Inherits: t1 +Has OIDs: no + +SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't1_inh'::regclass; + description +------------- + t1_a_check +(1 row) + +CREATE TABLE t13_inh () INHERITS (t1, t3); +NOTICE: merging multiple inherited definitions of column "a" +\d+ t13_inh + Table "public.t13_inh" + Column | Type | Modifiers | Storage | Description +--------+------+-----------+----------+------------- + a | text | not null | main | + b | text | | extended | + c | text | | external | +Check constraints: + "t1_a_check" CHECK (length(a) > 2) + "t3_a_check" CHECK (length(a) < 5) +Inherits: t1, + t3 +Has OIDs: no + +CREATE TABLE t13_like (LIKE t3 INCLUDING CONSTRAINTS INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (t1); +NOTICE: merging column "a" with inherited definition +\d+ t13_like + Table "public.t13_like" + Column | Type | Modifiers | Storage | Description +--------+------+-----------+----------+------------- + a | text | not null | main | A3 + b | text | | extended | + c | text | | external | C +Check constraints: + "t1_a_check" CHECK (length(a) > 2) + "t3_a_check" CHECK (length(a) < 5) +Inherits: t1 +Has OIDs: no + +SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't13_like'::regclass; + description +------------- + t3_a_check +(1 row) + +CREATE TABLE t_all (LIKE t1 INCLUDING ALL); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t_all_pkey" for table "t_all" +\d+ t_all + Table "public.t_all" + Column | Type | Modifiers | Storage | Description +--------+------+-----------+----------+------------- + a | text | not null | main | A + b | text | | extended | B +Indexes: + "t_all_pkey" PRIMARY KEY, btree (a) + "t_all_b_key" btree (b) + "t_all_key" btree ((a || b)) +Check constraints: + "t1_a_check" CHECK (length(a) > 2) +Has OIDs: no + +SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_class c WHERE classoid = 'pg_class'::regclass AND objoid = i.indexrelid AND c.oid = i.indexrelid AND i.indrelid = 't_all'::regclass ORDER BY c.relname, objsubid; + relname | objsubid | description +-------------+----------+--------------------- + t_all_b_key | 0 | index b_key + t_all_key | 1 | index column fnidx + t_all_pkey | 0 | index pkey + t_all_pkey | 1 | index column pkey.a +(4 rows) + +CREATE TABLE inh_error1 () INHERITS (t1, t4); +NOTICE: merging multiple inherited definitions of column "a" +ERROR: inherited column "a" has a storage parameter conflict +DETAIL: MAIN versus EXTENDED +CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGE) INHERITS (t1); +NOTICE: merging column "a" with inherited definition +ERROR: column "a" has a storage parameter conflict +DETAIL: MAIN versus EXTENDED +DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all; diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql index e68c658ec7..6f1a492676 100644 --- a/src/test/regress/sql/inherit.sql +++ b/src/test/regress/sql/inherit.sql @@ -287,3 +287,52 @@ create table cc2(f4 float) inherits(pp1,cc1); alter table pp1 add column a2 int check (a2 > 0); \d cc2 drop table pp1 cascade; + +-- including storage and comments +CREATE TABLE t1 (a text CHECK (length(a) > 2) PRIMARY KEY, b text); +CREATE INDEX t1_b_key ON t1 (b); +CREATE INDEX t1_fnidx ON t1 ((a || b)); +COMMENT ON COLUMN t1.a IS 'A'; +COMMENT ON COLUMN t1.b IS 'B'; +COMMENT ON CONSTRAINT t1_a_check ON t1 IS 't1_a_check'; +COMMENT ON INDEX t1_pkey IS 'index pkey'; +COMMENT ON INDEX t1_b_key IS 'index b_key'; +COMMENT ON COLUMN t1_pkey.a IS 'index column pkey.a'; +COMMENT ON COLUMN t1_fnidx.pg_expression_1 IS 'index column fnidx'; +ALTER TABLE t1 ALTER COLUMN a SET STORAGE MAIN; + +CREATE TABLE t2 (c text); +ALTER TABLE t2 ALTER COLUMN c SET STORAGE EXTERNAL; +COMMENT ON COLUMN t2.c IS 'C'; + +CREATE TABLE t3 (a text CHECK (length(a) < 5), c text); +ALTER TABLE t3 ALTER COLUMN c SET STORAGE EXTERNAL; +ALTER TABLE t3 ALTER COLUMN a SET STORAGE MAIN; +COMMENT ON COLUMN t3.a IS 'A3'; +COMMENT ON COLUMN t3.c IS 'C'; +COMMENT ON CONSTRAINT t3_a_check ON t3 IS 't3_a_check'; + +CREATE TABLE t4 (a text, c text); +ALTER TABLE t4 ALTER COLUMN c SET STORAGE EXTERNAL; + +CREATE TABLE t12_storage (LIKE t1 INCLUDING STORAGE, LIKE t2 INCLUDING STORAGE); +\d+ t12_storage +CREATE TABLE t12_comments (LIKE t1 INCLUDING COMMENTS, LIKE t2 INCLUDING COMMENTS); +\d+ t12_comments +CREATE TABLE t1_inh (LIKE t1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (t1); +\d+ t1_inh +SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't1_inh'::regclass; +CREATE TABLE t13_inh () INHERITS (t1, t3); +\d+ t13_inh +CREATE TABLE t13_like (LIKE t3 INCLUDING CONSTRAINTS INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (t1); +\d+ t13_like +SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't13_like'::regclass; + +CREATE TABLE t_all (LIKE t1 INCLUDING ALL); +\d+ t_all +SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_class c WHERE classoid = 'pg_class'::regclass AND objoid = i.indexrelid AND c.oid = i.indexrelid AND i.indrelid = 't_all'::regclass ORDER BY c.relname, objsubid; + +CREATE TABLE inh_error1 () INHERITS (t1, t4); +CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGE) INHERITS (t1); + +DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all; -- GitLab