diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 2d4dcd7556..abab55ba00 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -1753,6 +1753,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence, coldef->raw_default = restdef->raw_default; coldef->cooked_default = restdef->cooked_default; coldef->constraints = restdef->constraints; + coldef->identity = restdef->identity; coldef->is_from_type = false; list_delete_cell(schema, rest, prev); } @@ -2257,6 +2258,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence, coldef->cooked_default = restdef->cooked_default; coldef->constraints = restdef->constraints; coldef->is_from_parent = false; + coldef->identity = restdef->identity; list_delete_cell(schema, rest, prev); } else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 27e568fc62..cbc6f9b466 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -80,6 +80,7 @@ typedef struct bool isforeign; /* true if CREATE/ALTER FOREIGN TABLE */ bool isalter; /* true if altering existing table */ bool hasoids; /* does relation have an OID column? */ + Oid ofTypeOid; /* type used with CREATE TABLE OF */ List *columns; /* ColumnDef items */ List *ckconstraints; /* CHECK constraints */ List *fkconstraints; /* FOREIGN KEY constraints */ @@ -231,6 +232,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) cxt.rel = NULL; cxt.inhRelations = stmt->inhRelations; cxt.isalter = false; + cxt.ofTypeOid = InvalidOid; cxt.columns = NIL; cxt.ckconstraints = NIL; cxt.fkconstraints = NIL; @@ -240,6 +242,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) cxt.alist = NIL; cxt.pkey = NULL; cxt.ispartitioned = stmt->partspec != NULL; + cxt.partbound = stmt->partbound; /* * Notice that we allow OIDs here only for plain tables, even though @@ -661,8 +664,84 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) { Type ctype; Oid typeOid; + TypeName *typeName; + bool found_type = false; + int i; - ctype = typenameType(cxt->pstate, column->typeName, NULL); + /* + * Look at the type name of the column. When created + * under the context of CREATE TABLE OF, the column type + * name is not specified as part of the column clause, so + * do a lookup at the composite type defined and extract + * the correct type name from it. Columns listed in a + * child partition do not define a type name either, so + * look at the parent partition in this case and extract + * the type needed. Note that the type is needed beforehand + * so as serial evaluations can happen in a consistent way. + */ + if (OidIsValid(cxt->ofTypeOid)) + { + TupleDesc tupdesc; + + tupdesc = lookup_rowtype_tupdesc(cxt->ofTypeOid, -1); + for (i = 0; i < tupdesc->natts ; i++) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, i); + + if (attr->attisdropped) + continue; + + if (strcmp(NameStr(attr->attname), + column->colname) == 0) + { + typeName = makeTypeNameFromOid(attr->atttypid, + attr->atttypmod); + found_type = true; + break; + } + } + DecrTupleDescRefCount(tupdesc); + } + else if (cxt->partbound) + { + Relation rel; + RangeVar *inh = linitial_node(RangeVar, + cxt->inhRelations); + + Assert(list_length(cxt->inhRelations) == 1); + + rel = heap_openrv(inh, AccessShareLock); + for (i = 0; i < rel->rd_att->natts; i++) + { + Form_pg_attribute attr = + TupleDescAttr(rel->rd_att, i); + + if (attr->attisdropped) + continue; + if (strcmp(NameStr(attr->attname), + column->colname) == 0) + { + typeName = makeTypeNameFromOid(attr->atttypid, + attr->atttypmod); + found_type = true; + break; + } + } + heap_close(rel, NoLock); + } + else + { + typeName = column->typeName; + found_type = true; + } + + if (!found_type) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" does not exist", + column->colname))); + + ctype = typenameType(cxt->pstate, typeName, NULL); typeOid = HeapTupleGetOid(ctype); ReleaseSysCache(ctype); @@ -1217,6 +1296,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename) check_of_type(tuple); ofTypeId = HeapTupleGetOid(tuple); ofTypename->typeOid = ofTypeId; /* cached for later */ + cxt->ofTypeOid = ofTypeId; tupdesc = lookup_rowtype_tupdesc(ofTypeId, -1); for (i = 0; i < tupdesc->natts; i++) @@ -2686,6 +2766,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, cxt.rel = rel; cxt.inhRelations = NIL; cxt.isalter = true; + cxt.ofTypeOid = InvalidOid; cxt.hasoids = false; /* need not be right */ cxt.columns = NIL; cxt.ckconstraints = NIL; diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out index 5fa585d6cc..5a6ba61da9 100644 --- a/src/test/regress/expected/identity.out +++ b/src/test/regress/expected/identity.out @@ -333,3 +333,38 @@ SELECT * FROM itest8; RESET ROLE; DROP TABLE itest8; DROP USER regress_user1; +-- typed table +CREATE TYPE itest_type AS (f1 integer, f2 text, f3 bigint); +CREATE TABLE itest9 OF itest_type ( + f4 WITH OPTIONS GENERATED ALWAYS AS IDENTITY); -- error +ERROR: column "f4" does not exist +CREATE TABLE itest9 OF itest_type ( + f2 WITH OPTIONS GENERATED ALWAYS AS IDENTITY); -- error +ERROR: identity column type must be smallint, integer, or bigint +CREATE TABLE itest9 OF itest_type ( + f1 WITH OPTIONS GENERATED ALWAYS AS IDENTITY); -- ok +\d itest9 + Table "public.itest9" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------ + f1 | integer | | not null | generated always as identity + f2 | text | | | + f3 | bigint | | | +Typed table of type: itest_type + +DROP TYPE itest_type CASCADE; +NOTICE: drop cascades to table itest9 +-- partitioned tables +CREATE TABLE itest_parent (f1 date NOT NULL, f2 int) PARTITION BY RANGE (f1); +CREATE TABLE itest_child PARTITION OF itest_parent ( + f2 WITH OPTIONS GENERATED ALWAYS AS IDENTITY +) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); +\d itest_child + Table "public.itest_child" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------ + f1 | date | | not null | + f2 | integer | | not null | generated always as identity +Partition of: itest_parent FOR VALUES FROM ('07-01-2016') TO ('08-01-2016') + +DROP TABLE itest_parent CASCADE; diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql index e1b5a074c9..734fac70d0 100644 --- a/src/test/regress/sql/identity.sql +++ b/src/test/regress/sql/identity.sql @@ -194,3 +194,22 @@ SELECT * FROM itest8; RESET ROLE; DROP TABLE itest8; DROP USER regress_user1; + +-- typed table +CREATE TYPE itest_type AS (f1 integer, f2 text, f3 bigint); +CREATE TABLE itest9 OF itest_type ( + f4 WITH OPTIONS GENERATED ALWAYS AS IDENTITY); -- error +CREATE TABLE itest9 OF itest_type ( + f2 WITH OPTIONS GENERATED ALWAYS AS IDENTITY); -- error +CREATE TABLE itest9 OF itest_type ( + f1 WITH OPTIONS GENERATED ALWAYS AS IDENTITY); -- ok +\d itest9 +DROP TYPE itest_type CASCADE; + +-- partitioned tables +CREATE TABLE itest_parent (f1 date NOT NULL, f2 int) PARTITION BY RANGE (f1); +CREATE TABLE itest_child PARTITION OF itest_parent ( + f2 WITH OPTIONS GENERATED ALWAYS AS IDENTITY +) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); +\d itest_child +DROP TABLE itest_parent CASCADE;