From e440e12c562432a2a695b8054964fb34e3bd823e Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Sun, 26 Sep 2010 14:41:03 +0300 Subject: [PATCH] Add ALTER TYPE ... ADD/DROP/ALTER/RENAME ATTRIBUTE Like with tables, this also requires allowing the existence of composite types with zero attributes. reviewed by KaiGai Kohei --- doc/src/sgml/ref/alter_type.sgml | 130 ++++++++++- doc/src/sgml/ref/create_type.sgml | 22 +- doc/src/sgml/ref/drop_type.sgml | 4 +- src/backend/commands/alter.c | 2 + src/backend/commands/tablecmds.c | 257 +++++++++++++++------- src/backend/commands/typecmds.c | 5 - src/backend/parser/gram.y | 165 +++++++++++--- src/backend/parser/parse_utilcmd.c | 3 + src/backend/tcop/utility.c | 4 + src/bin/pg_dump/pg_dump.c | 12 +- src/bin/psql/tab-complete.c | 34 ++- src/include/nodes/parsenodes.h | 1 + src/include/parser/kwlist.h | 1 + src/test/regress/expected/alter_table.out | 89 +++++++- src/test/regress/sql/alter_table.sql | 55 +++++ 15 files changed, 636 insertions(+), 148 deletions(-) diff --git a/doc/src/sgml/ref/alter_type.sgml b/doc/src/sgml/ref/alter_type.sgml index d76feb48c3..315922ea83 100644 --- a/doc/src/sgml/ref/alter_type.sgml +++ b/doc/src/sgml/ref/alter_type.sgml @@ -23,9 +23,17 @@ PostgreSQL documentation -ALTER TYPE name RENAME TO new_name +ALTER TYPE name action [, ... ] ALTER TYPE name OWNER TO new_owner +ALTER TYPE name RENAME ATTRIBUTE attribute_name TO new_attribute_name +ALTER TYPE name RENAME TO new_name ALTER TYPE name SET SCHEMA new_schema + +where action is one of: + + ADD ATTRIBUTE attribute_name data_type + DROP ATTRIBUTE [ IF EXISTS ] attribute_name + ALTER ATTRIBUTE attribute_name [ SET DATA ] TYPE data_type @@ -34,6 +42,76 @@ ALTER TYPE name SET SCHEMA ALTER TYPE changes the definition of an existing type. + There are several subforms: + + + + ADD ATTRIBUTE + + + This form adds a new attribute to a composite type, using the same syntax as + . + + + + + + DROP ATTRIBUTE [ IF EXISTS ] + + + This form drops an attribute from a composite type. + If IF EXISTS is specified and the attribute + does not exist, no error is thrown. In this case a notice + is issued instead. + + + + + + SET DATA TYPE + + + This form changes the type of an attribute of a composite type. + + + + + + OWNER + + + This form changes the owner of the type. + + + + + + RENAME + + + This form changes the name of the type or the name of an + individual attribute of a composite type. + + + + + + SET SCHEMA + + + This form moves the type into another schema. + + + + + + + + The ADD ATTRIBUTE, DROP + ATTRIBUTE, and ALTER ATTRIBUTE actions + can be combined into a list of multiple alterations to apply in + parallel. For example, it is possible to add several attributes + and/or alter the type of several attributes in a single command. @@ -90,6 +168,34 @@ ALTER TYPE name SET SCHEMA + + attribute_name + + + The name of the attribute to add, alter, or drop. + + + + + + new_attribute_name + + + The new name of the attribute begin renamed. + + + + + + data_type + + + The data type of the attribute to add, or the new type of the + attribute to alter. + + + + @@ -117,16 +223,32 @@ ALTER TYPE email OWNER TO joe; to customers: ALTER TYPE email SET SCHEMA customers; + + + + + To add a new attribute to a type: + +ALTER TYPE compfoo ADD ATTRIBUTE f3 int; Compatibility - + - There is no ALTER TYPE statement in the SQL - standard. + The variants to add and drop attributes are part of the SQL + standard; the other variants are PostgreSQL extensions. + + + See Also + + + + + + diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index ca521b387b..a3c75b51d0 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -22,7 +22,7 @@ PostgreSQL documentation CREATE TYPE name AS - ( attribute_name data_type [, ... ] ) + ( [ attribute_name data_type [, ... ] ] ) CREATE TYPE name AS ENUM ( [ 'label' [, ... ] ] ) @@ -768,10 +768,18 @@ CREATE TABLE big_objs ( Compatibility - This CREATE TYPE command is a - PostgreSQL extension. There is a - CREATE TYPE statement in the SQL standard - that is rather different in detail. + The first form of the CREATE TYPE command, which + creates a composite type, conforms to the SQL standard. + The other forms are PostgreSQL + extensions. The CREATE TYPE statement in + the SQL standard also defines other forms that are not + implemented in PostgreSQL. + + + + The ability to create a composite type with zero attributes is + a PostgreSQL-specific deviation from the + standard (analogous to CREATE TABLE). @@ -779,10 +787,10 @@ CREATE TABLE big_objs ( See Also - - + + diff --git a/doc/src/sgml/ref/drop_type.sgml b/doc/src/sgml/ref/drop_type.sgml index 10e3e143bc..76e31517a1 100644 --- a/doc/src/sgml/ref/drop_type.sgml +++ b/doc/src/sgml/ref/drop_type.sgml @@ -97,7 +97,7 @@ DROP TYPE box; This command is similar to the corresponding command in the SQL standard, apart from the IF EXISTS option, which is a PostgreSQL extension. - But note that the CREATE TYPE command + But note that much of the CREATE TYPE command and the data type extension mechanisms in PostgreSQL differ from the SQL standard. @@ -107,8 +107,8 @@ DROP TYPE box; See Also - + diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index a315096fa4..794d355944 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -89,6 +89,7 @@ ExecRenameStmt(RenameStmt *stmt) case OBJECT_VIEW: case OBJECT_INDEX: case OBJECT_COLUMN: + case OBJECT_ATTRIBUTE: case OBJECT_TRIGGER: { Oid relid; @@ -123,6 +124,7 @@ ExecRenameStmt(RenameStmt *stmt) break; } case OBJECT_COLUMN: + case OBJECT_ATTRIBUTE: renameatt(relid, stmt->subname, /* old att name */ stmt->newname, /* new att name */ diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 82143682d5..a5d7af6042 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -263,12 +263,13 @@ static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, static void ATRewriteTables(List **wqueue, LOCKMODE lockmode); static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode); static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel); -static void ATSimplePermissions(Relation rel, bool allowView); +static void ATSimplePermissions(Relation rel, bool allowView, bool allowType); static void ATSimplePermissionsRelationOrIndex(Relation rel); static void ATSimpleRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode); static void ATOneLevelRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd, LOCKMODE lockmode); +static void find_typed_table_dependencies(Oid typeOid, const char *typeName); static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOCKMODE lockmode); static void ATExecAddColumn(AlteredTableInfo *tab, Relation rel, @@ -1978,6 +1979,10 @@ renameatt(Oid myrelid, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot rename column of typed table"))); + if (targetrelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) + find_typed_table_dependencies(targetrelation->rd_rel->reltype, + RelationGetRelationName(targetrelation)); + /* * Renaming the columns of sequences or toast tables doesn't actually * break anything from the system's point of view, since internal @@ -2368,8 +2373,13 @@ AlterTable(AlterTableStmt *stmt) /* * For mostly-historical reasons, we allow ALTER TABLE to apply to - * all relation types. + * almost all relation types. */ + if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table", + RelationGetRelationName(rel)))); break; case OBJECT_INDEX: @@ -2388,6 +2398,14 @@ AlterTable(AlterTableStmt *stmt) RelationGetRelationName(rel)))); break; + case OBJECT_TYPE: + if (rel->rd_rel->relkind != RELKIND_COMPOSITE_TYPE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a composite type", + RelationGetRelationName(rel)))); + break; + case OBJECT_VIEW: if (rel->rd_rel->relkind != RELKIND_VIEW) ereport(ERROR, @@ -2639,14 +2657,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, switch (cmd->subtype) { case AT_AddColumn: /* ADD COLUMN */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, false, true); /* Performs own recursion */ ATPrepAddColumn(wqueue, rel, recurse, cmd, lockmode); pass = AT_PASS_ADD_COL; break; case AT_AddColumnToView: /* add column via CREATE OR REPLACE * VIEW */ - ATSimplePermissions(rel, true); + ATSimplePermissions(rel, true, false); /* Performs own recursion */ ATPrepAddColumn(wqueue, rel, recurse, cmd, lockmode); pass = AT_PASS_ADD_COL; @@ -2659,19 +2677,19 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, * substitutes default values into INSERTs before it expands * rules. */ - ATSimplePermissions(rel, true); + ATSimplePermissions(rel, true, false); ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); /* No command-specific prep needed */ pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP; break; case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, false, false); ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); /* No command-specific prep needed */ pass = AT_PASS_DROP; break; case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, false, false); ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); /* No command-specific prep needed */ pass = AT_PASS_ADD_CONSTR; @@ -2689,25 +2707,25 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, pass = AT_PASS_MISC; break; case AT_SetStorage: /* ALTER COLUMN SET STORAGE */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, false, false); ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); /* No command-specific prep needed */ pass = AT_PASS_MISC; break; case AT_DropColumn: /* DROP COLUMN */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, false, true); ATPrepDropColumn(rel, recurse, cmd); /* Recursion occurs during execution phase */ pass = AT_PASS_DROP; break; case AT_AddIndex: /* ADD INDEX */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, false, false); /* This command never recurses */ /* No command-specific prep needed */ pass = AT_PASS_ADD_INDEX; break; case AT_AddConstraint: /* ADD CONSTRAINT */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, false, false); /* Recursion occurs during execution phase */ /* No command-specific prep needed except saving recurse flag */ if (recurse) @@ -2715,7 +2733,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, pass = AT_PASS_ADD_CONSTR; break; case AT_DropConstraint: /* DROP CONSTRAINT */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, false, false); /* Recursion occurs during execution phase */ /* No command-specific prep needed except saving recurse flag */ if (recurse) @@ -2723,7 +2741,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, pass = AT_PASS_DROP; break; case AT_AlterColumnType: /* ALTER COLUMN TYPE */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, false, true); /* Performs own recursion */ ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd, lockmode); pass = AT_PASS_ALTER_TYPE; @@ -2735,20 +2753,20 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, break; case AT_ClusterOn: /* CLUSTER ON */ case AT_DropCluster: /* SET WITHOUT CLUSTER */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, false, false); /* These commands never recurse */ /* No command-specific prep needed */ pass = AT_PASS_MISC; break; case AT_AddOids: /* SET WITH OIDS */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, false, false); /* Performs own recursion */ if (!rel->rd_rel->relhasoids || recursing) ATPrepAddOids(wqueue, rel, recurse, cmd, lockmode); pass = AT_PASS_ADD_COL; break; case AT_DropOids: /* SET WITHOUT OIDS */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, false, false); /* Performs own recursion */ if (rel->rd_rel->relhasoids) { @@ -2775,7 +2793,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, pass = AT_PASS_MISC; break; case AT_AddInherit: /* INHERIT */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, false, false); /* This command never recurses */ ATPrepAddInherit(rel); pass = AT_PASS_MISC; @@ -2793,7 +2811,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, case AT_EnableReplicaRule: case AT_DisableRule: case AT_DropInherit: /* NO INHERIT */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, false, false); /* These commands never recurse */ /* No command-specific prep needed */ pass = AT_PASS_MISC; @@ -3519,7 +3537,7 @@ ATGetQueueEntry(List **wqueue, Relation rel) * - Ensure that it is not a system table */ static void -ATSimplePermissions(Relation rel, bool allowView) +ATSimplePermissions(Relation rel, bool allowView, bool allowType) { if (rel->rd_rel->relkind != RELKIND_RELATION) { @@ -3531,6 +3549,14 @@ ATSimplePermissions(Relation rel, bool allowView) errmsg("\"%s\" is not a table or view", RelationGetRelationName(rel)))); } + else if (allowType) + { + if (rel->rd_rel->relkind != RELKIND_COMPOSITE_TYPE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table or composite type", + RelationGetRelationName(rel)))); + } else ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -3758,6 +3784,44 @@ find_composite_type_dependencies(Oid typeOid, } +/* + * find_typed_table_dependencies + * + * Check to see if a composite type is being used as the type of a + * typed table. Eventually, we'd like to propagate the alter + * operation into such tables, but for now, just error out if we find + * any. + */ +static void +find_typed_table_dependencies(Oid typeOid, const char *typeName) +{ + Relation classRel; + ScanKeyData key[1]; + HeapScanDesc scan; + HeapTuple tuple; + + classRel = heap_open(RelationRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_class_reloftype, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(typeOid)); + + scan = heap_beginscan(classRel, SnapshotNow, 1, key); + + if (HeapTupleIsValid(tuple = heap_getnext(scan, ForwardScanDirection))) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter type \"%s\" because it is the type of a typed table", + typeName))); + } + + heap_endscan(scan); + heap_close(classRel, AccessShareLock); +} + + /* * ALTER TABLE ADD COLUMN * @@ -3804,6 +3868,10 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("column must be added to child tables too"))); } + + if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) + find_typed_table_dependencies(rel->rd_rel->reltype, + RelationGetRelationName(rel)); } static void @@ -4007,7 +4075,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel, * defaults, not even for domain-typed columns. And in any case we * mustn't invoke Phase 3 on a view, since it has no storage. */ - if (relkind != RELKIND_VIEW && attribute.attnum > 0) + if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE && attribute.attnum > 0) { defval = (Expr *) build_column_default(rel, attribute.attnum); @@ -4535,6 +4603,10 @@ ATPrepDropColumn(Relation rel, bool recurse, AlterTableCmd *cmd) (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot drop column from typed table"))); + if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) + find_typed_table_dependencies(rel->rd_rel->reltype, + RelationGetRelationName(rel)); + /* No command-specific prep needed except saving recurse flag */ if (recurse) cmd->subtype = AT_DropColumnRecurse; @@ -4554,7 +4626,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName, /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, false, true); /* * get the number of the attribute @@ -4858,7 +4930,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, false, false); /* * Call AddRelationNewConstraints to do the work, making sure it works on @@ -5801,7 +5873,7 @@ ATExecDropConstraint(Relation rel, const char *constrName, /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, false, false); conrel = heap_open(ConstraintRelationId, RowExclusiveLock); @@ -6033,77 +6105,94 @@ ATPrepAlterColumnType(List **wqueue, /* make sure datatype is legal for a column */ CheckAttributeType(colName, targettype, false); - /* - * Set up an expression to transform the old data value to the new type. - * If a USING option was given, transform and use that expression, else - * just take the old value and try to coerce it. We do this first so that - * type incompatibility can be detected before we waste effort, and - * because we need the expression to be parsed against the original table - * rowtype. - */ - if (cmd->transform) + if (tab->relkind == RELKIND_RELATION) { - RangeTblEntry *rte; + /* + * Set up an expression to transform the old data value to the new type. + * If a USING option was given, transform and use that expression, else + * just take the old value and try to coerce it. We do this first so that + * type incompatibility can be detected before we waste effort, and + * because we need the expression to be parsed against the original table + * rowtype. + */ + if (cmd->transform) + { + RangeTblEntry *rte; - /* Expression must be able to access vars of old table */ - rte = addRangeTableEntryForRelation(pstate, - rel, - NULL, - false, - true); - addRTEtoQuery(pstate, rte, false, true, true); + /* Expression must be able to access vars of old table */ + rte = addRangeTableEntryForRelation(pstate, + rel, + NULL, + false, + true); + addRTEtoQuery(pstate, rte, false, true, true); - transform = transformExpr(pstate, cmd->transform); + transform = transformExpr(pstate, cmd->transform); - /* It can't return a set */ - if (expression_returns_set(transform)) + /* It can't return a set */ + if (expression_returns_set(transform)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("transform expression must not return a set"))); + + /* No subplans or aggregates, either... */ + if (pstate->p_hasSubLinks) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot use subquery in transform expression"))); + if (pstate->p_hasAggs) + ereport(ERROR, + (errcode(ERRCODE_GROUPING_ERROR), + errmsg("cannot use aggregate function in transform expression"))); + if (pstate->p_hasWindowFuncs) + ereport(ERROR, + (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("cannot use window function in transform expression"))); + } + else + { + transform = (Node *) makeVar(1, attnum, + attTup->atttypid, attTup->atttypmod, + 0); + } + + transform = coerce_to_target_type(pstate, + transform, exprType(transform), + targettype, targettypmod, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (transform == NULL) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("transform expression must not return a set"))); + errmsg("column \"%s\" cannot be cast to type %s", + colName, format_type_be(targettype)))); - /* No subplans or aggregates, either... */ - if (pstate->p_hasSubLinks) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot use subquery in transform expression"))); - if (pstate->p_hasAggs) - ereport(ERROR, - (errcode(ERRCODE_GROUPING_ERROR), - errmsg("cannot use aggregate function in transform expression"))); - if (pstate->p_hasWindowFuncs) - ereport(ERROR, - (errcode(ERRCODE_WINDOWING_ERROR), - errmsg("cannot use window function in transform expression"))); + /* + * Add a work queue item to make ATRewriteTable update the column + * contents. + */ + newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue)); + newval->attnum = attnum; + newval->expr = (Expr *) transform; + + tab->newvals = lappend(tab->newvals, newval); } - else + + if (tab->relkind == RELKIND_COMPOSITE_TYPE) { - transform = (Node *) makeVar(1, attnum, - attTup->atttypid, attTup->atttypmod, - 0); + /* + * For composite types, do this check now. Tables will check + * it later when the table is being rewritten. + */ + find_composite_type_dependencies(rel->rd_rel->reltype, + NULL, + RelationGetRelationName(rel)); + + find_typed_table_dependencies(rel->rd_rel->reltype, + RelationGetRelationName(rel)); } - transform = coerce_to_target_type(pstate, - transform, exprType(transform), - targettype, targettypmod, - COERCION_ASSIGNMENT, - COERCE_IMPLICIT_CAST, - -1); - if (transform == NULL) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("column \"%s\" cannot be cast to type %s", - colName, format_type_be(targettype)))); - - /* - * Add a work queue item to make ATRewriteTable update the column - * contents. - */ - newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue)); - newval->attnum = attnum; - newval->expr = (Expr *) transform; - - tab->newvals = lappend(tab->newvals, newval); - ReleaseSysCache(tuple); /* @@ -7367,7 +7456,7 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) * Must be owner of both parent and child -- child was checked by * ATSimplePermissions call in ATPrepCmd */ - ATSimplePermissions(parent_rel, false); + ATSimplePermissions(parent_rel, false, false); /* Permanent rels cannot inherit from temporary ones */ if (parent_rel->rd_istemp && !child_rel->rd_istemp) diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 87fcf29bf2..25503bda4f 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -1508,11 +1508,6 @@ DefineCompositeType(const RangeVar *typevar, List *coldeflist) Oid typeNamespace; Oid relid; - if (coldeflist == NIL) - ereport(ERROR, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("composite type must have at least one attribute"))); - /* * now set the parameters for keys/inheritance etc. All of these are * uninteresting for composite types... diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 22ce35382b..40bd7a3932 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -131,6 +131,7 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args, int location); static List *mergeTableFuncParameters(List *func_args, List *columns); static TypeName *TableFuncTypeName(List *columns); +static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner); %} @@ -184,7 +185,7 @@ static TypeName *TableFuncTypeName(List *columns); AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterFdwStmt AlterForeignServerStmt AlterGroupStmt AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt - AlterUserStmt AlterUserMappingStmt AlterUserSetStmt + AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt AlterRoleStmt AlterRoleSetStmt AlterDefaultPrivilegesStmt DefACLAction AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt @@ -218,8 +219,8 @@ static TypeName *TableFuncTypeName(List *columns); %type alter_column_default opclass_item opclass_drop alter_using %type add_drop opt_asc_desc opt_nulls_order -%type alter_table_cmd -%type alter_table_cmds +%type alter_table_cmd alter_type_cmd +%type alter_table_cmds alter_type_cmds %type opt_drop_behavior @@ -295,7 +296,7 @@ static TypeName *TableFuncTypeName(List *columns); reloption_list group_clause TriggerFuncArgs select_limit opt_select_limit opclass_item_list opclass_drop_list opt_opfamily transaction_mode_list_or_empty - TableFuncElementList opt_type_modifiers + OptTableFuncElementList TableFuncElementList opt_type_modifiers prep_type_clause execute_param_clause using_clause returning_clause opt_enum_val_list enum_val_list table_func_column_list @@ -462,7 +463,7 @@ static TypeName *TableFuncTypeName(List *columns); /* ordinary key words in alphabetical order */ %token ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC - ASSERTION ASSIGNMENT ASYMMETRIC AT AUTHORIZATION + ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT BOOLEAN_P BOTH BY @@ -657,6 +658,7 @@ stmt : | AlterOwnerStmt | AlterSeqStmt | AlterTableStmt + | AlterCompositeTypeStmt | AlterRoleSetStmt | AlterRoleStmt | AlterTSConfigurationStmt @@ -1968,6 +1970,72 @@ reloption_elem: ; +/***************************************************************************** + * + * ALTER TYPE + * + * really variants of the ALTER TABLE subcommands with different spellings + *****************************************************************************/ + +AlterCompositeTypeStmt: + ALTER TYPE_P any_name alter_type_cmds + { + AlterTableStmt *n = makeNode(AlterTableStmt); + + /* can't use qualified_name, sigh */ + n->relation = makeRangeVarFromAnyName($3, @3, yyscanner); + n->cmds = $4; + n->relkind = OBJECT_TYPE; + $$ = (Node *)n; + } + ; + +alter_type_cmds: + alter_type_cmd { $$ = list_make1($1); } + | alter_type_cmds ',' alter_type_cmd { $$ = lappend($1, $3); } + ; + +alter_type_cmd: + /* ALTER TYPE ADD ATTRIBUTE */ + ADD_P ATTRIBUTE TableFuncElement + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_AddColumn; + n->def = $3; + $$ = (Node *)n; + } + /* ALTER TYPE DROP ATTRIBUTE IF EXISTS */ + | DROP ATTRIBUTE IF_P EXISTS ColId + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_DropColumn; + n->name = $5; + n->behavior = DROP_RESTRICT; /* currently no effect */ + n->missing_ok = TRUE; + $$ = (Node *)n; + } + /* ALTER TYPE DROP ATTRIBUTE */ + | DROP ATTRIBUTE ColId opt_drop_behavior + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_DropColumn; + n->name = $3; + n->behavior = DROP_RESTRICT; /* currently no effect */ + n->missing_ok = FALSE; + $$ = (Node *)n; + } + /* ALTER TYPE ALTER ATTRIBUTE [SET DATA] TYPE */ + | ALTER ATTRIBUTE ColId opt_set_data TYPE_P Typename + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_AlterColumnType; + n->name = $3; + n->def = (Node *) $6; + $$ = (Node *)n; + } + ; + + /***************************************************************************** * * QUERY : @@ -3678,39 +3746,12 @@ DefineStmt: n->definition = NIL; $$ = (Node *)n; } - | CREATE TYPE_P any_name AS '(' TableFuncElementList ')' + | CREATE TYPE_P any_name AS '(' OptTableFuncElementList ')' { CompositeTypeStmt *n = makeNode(CompositeTypeStmt); - RangeVar *r = makeNode(RangeVar); /* can't use qualified_name, sigh */ - switch (list_length($3)) - { - case 1: - r->catalogname = NULL; - r->schemaname = NULL; - r->relname = strVal(linitial($3)); - break; - case 2: - r->catalogname = NULL; - r->schemaname = strVal(linitial($3)); - r->relname = strVal(lsecond($3)); - break; - case 3: - r->catalogname = strVal(linitial($3)); - r->schemaname = strVal(lsecond($3)); - r->relname = strVal(lthird($3)); - break; - default: - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("improper qualified name (too many dotted names): %s", - NameListToString($3)), - parser_errposition(@3))); - break; - } - r->location = @3; - n->typevar = r; + n->typevar = makeRangeVarFromAnyName($3, @3, yyscanner); n->coldeflist = $6; $$ = (Node *)n; } @@ -5836,6 +5877,15 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name n->newname = $6; $$ = (Node *)n; } + | ALTER TYPE_P any_name RENAME ATTRIBUTE name TO name + { + RenameStmt *n = makeNode(RenameStmt); + n->renameType = OBJECT_ATTRIBUTE; + n->relation = makeRangeVarFromAnyName($3, @3, yyscanner); + n->subname = $6; + n->newname = $8; + $$ = (Node *)n; + } ; opt_column: COLUMN { $$ = COLUMN; } @@ -8216,6 +8266,11 @@ where_or_current_clause: ; +OptTableFuncElementList: + TableFuncElementList { $$ = $1; } + | /*EMPTY*/ { $$ = NIL; } + ; + TableFuncElementList: TableFuncElement { @@ -10897,6 +10952,7 @@ unreserved_keyword: | ASSERTION | ASSIGNMENT | AT + | ATTRIBUTE | BACKWARD | BEFORE | BEGIN_P @@ -11857,6 +11913,47 @@ TableFuncTypeName(List *columns) return result; } +/* + * Convert a list of (dotted) names to a RangeVar (like + * makeRangeVarFromNameList, but with position support). The + * "AnyName" refers to the any_name production in the grammar. + */ +static RangeVar * +makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner) +{ + RangeVar *r = makeNode(RangeVar); + + switch (list_length(names)) + { + case 1: + r->catalogname = NULL; + r->schemaname = NULL; + r->relname = strVal(linitial(names)); + break; + case 2: + r->catalogname = NULL; + r->schemaname = strVal(linitial(names)); + r->relname = strVal(lsecond(names)); + break; + case 3: + r->catalogname = strVal(linitial(names));; + r->schemaname = strVal(lsecond(names)); + r->relname = strVal(lthird(names)); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("improper qualified name (too many dotted names): %s", + NameListToString(names)), + parser_errposition(position))); + break; + } + + r->location = position; + + return r; +} + /* * Must undefine this stuff before including scan.c, since it has different * definitions for these macros. diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 552a6af8f8..37ca331c21 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -839,6 +839,9 @@ transformOfType(ParseState *pstate, CreateStmtContext *cxt, TypeName *ofTypename Form_pg_attribute attr = tupdesc->attrs[i]; ColumnDef *n = makeNode(ColumnDef); + if (attr->attisdropped) + continue; + n->colname = pstrdup(NameStr(attr->attname)); n->typeName = makeTypeNameFromOid(attr->atttypid, attr->atttypmod); n->constraints = NULL; diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 1865f843d2..f69559f0e4 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1657,6 +1657,7 @@ CreateCommandTag(Node *parsetree) case OBJECT_TSCONFIGURATION: tag = "ALTER TEXT SEARCH CONFIGURATION"; break; + case OBJECT_ATTRIBUTE: case OBJECT_TYPE: tag = "ALTER TYPE"; break; @@ -1780,6 +1781,9 @@ CreateCommandTag(Node *parsetree) case OBJECT_SEQUENCE: tag = "ALTER SEQUENCE"; break; + case OBJECT_TYPE: + tag = "ALTER TYPE"; + break; case OBJECT_VIEW: tag = "ALTER VIEW"; break; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 06c1f3ae60..2e1486f221 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -7249,13 +7249,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo) res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); - /* Expecting at least a single result */ ntups = PQntuples(res); - if (ntups < 1) - { - write_msg(NULL, "query returned no rows: %s\n", query->data); - exit_nicely(); - } i_attname = PQfnumber(res, "attname"); i_atttypdefn = PQfnumber(res, "atttypdefn"); @@ -7356,12 +7350,12 @@ dumpCompositeTypeColComments(Archive *fout, TypeInfo *tyinfo) res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); - /* Expecting at least a single result */ ntups = PQntuples(res); if (ntups < 1) { - write_msg(NULL, "query returned no rows: %s\n", query->data); - exit_nicely(); + PQclear(res); + destroyPQExpBuffer(query); + return; } pgClassOid = atooid(PQgetvalue(res, 0, PQfnumber(res, "tableoid"))); diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 3efa97ad88..04c81844da 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1253,15 +1253,45 @@ psql_completion(char *text, int start, int end) COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH3); } - /* complete ALTER TYPE with OWNER TO, SET SCHEMA */ + /* complete ALTER TYPE with actions */ else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && pg_strcasecmp(prev2_wd, "TYPE") == 0) { static const char *const list_ALTERTYPE[] = - {"OWNER TO", "RENAME TO", "SET SCHEMA", NULL}; + {"ADD ATTRIBUTE", "ALTER ATTRIBUTE", "DROP ATTRIBUTE", + "OWNER TO", "RENAME", "SET SCHEMA", NULL}; COMPLETE_WITH_LIST(list_ALTERTYPE); } + /* ALTER TYPE RENAME */ + else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 && + pg_strcasecmp(prev3_wd, "TYPE") == 0 && + pg_strcasecmp(prev_wd, "RENAME") == 0) + { + static const char *const list_ALTERTYPE[] = + {"ATTRIBUTE", "TO", NULL}; + + COMPLETE_WITH_LIST(list_ALTERTYPE); + } + /* ALTER TYPE xxx RENAME ATTRIBUTE yyy */ + else if (pg_strcasecmp(prev5_wd, "TYPE") == 0 && + pg_strcasecmp(prev3_wd, "RENAME") == 0 && + pg_strcasecmp(prev2_wd, "ATTRIBUTE") == 0) + COMPLETE_WITH_CONST("TO"); + + /* If we have TYPE ALTER/DROP/RENAME ATTRIBUTE, provide list of attributes */ + else if (pg_strcasecmp(prev4_wd, "TYPE") == 0 && + (pg_strcasecmp(prev2_wd, "ALTER") == 0 || + pg_strcasecmp(prev2_wd, "DROP") == 0 || + pg_strcasecmp(prev2_wd, "RENAME") == 0) && + pg_strcasecmp(prev_wd, "ATTRIBUTE") == 0) + COMPLETE_WITH_ATTR(prev3_wd, ""); + /* ALTER TYPE ALTER ATTRIBUTE */ + else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 && + pg_strcasecmp(prev2_wd, "ATTRIBUTE") == 0)) + { + COMPLETE_WITH_CONST("TYPE"); + } /* complete ALTER GROUP */ else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && pg_strcasecmp(prev2_wd, "GROUP") == 0) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index b67ab68298..68de95e49f 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1046,6 +1046,7 @@ typedef struct SetOperationStmt typedef enum ObjectType { OBJECT_AGGREGATE, + OBJECT_ATTRIBUTE, /* type's attribute, when distinct from column */ OBJECT_CAST, OBJECT_COLUMN, OBJECT_CONSTRAINT, diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 5e3ccd5ee2..01005d83c2 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD) PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD) PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD) PG_KEYWORD("at", AT, UNRESERVED_KEYWORD) +PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD) PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD) PG_KEYWORD("before", BEFORE, UNRESERVED_KEYWORD) diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 83e24fd8c9..ab19a8e4fc 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -854,7 +854,7 @@ select * from myview; (0 rows) alter table myview drop d; -ERROR: "myview" is not a table +ERROR: "myview" is not a table or composite type drop view myview; -- test some commands to make sure they fail on the dropped column analyze atacc1(a); @@ -1472,6 +1472,11 @@ select * from another; (3 rows) drop table another; +-- table's row type +create table tab1 (a int, b text); +create table tab2 (x int, y tab1); +alter table tab1 alter column b type varchar; -- fails +ERROR: cannot alter table "tab1" because column "tab2"."y" uses its rowtype -- -- lock levels -- @@ -1683,3 +1688,85 @@ drop cascades to view alter2.v1 drop cascades to function alter2.plus1(integer) drop cascades to type alter2.posint drop cascades to type alter2.ctype +-- +-- composite types +-- +CREATE TYPE test_type AS (a int); +\d test_type +Composite type "public.test_type" + Column | Type +--------+--------- + a | integer + +ALTER TYPE nosuchtype ADD ATTRIBUTE b text; -- fails +ERROR: relation "nosuchtype" does not exist +ALTER TYPE test_type ADD ATTRIBUTE b text; +\d test_type +Composite type "public.test_type" + Column | Type +--------+--------- + a | integer + b | text + +ALTER TYPE test_type ADD ATTRIBUTE b text; -- fails +ERROR: column "b" of relation "test_type" already exists +ALTER TYPE test_type ALTER ATTRIBUTE b SET DATA TYPE varchar; +\d test_type +Composite type "public.test_type" + Column | Type +--------+------------------- + a | integer + b | character varying + +ALTER TYPE test_type ALTER ATTRIBUTE b SET DATA TYPE integer; +\d test_type +Composite type "public.test_type" + Column | Type +--------+--------- + a | integer + b | integer + +ALTER TYPE test_type DROP ATTRIBUTE b; +\d test_type +Composite type "public.test_type" + Column | Type +--------+--------- + a | integer + +ALTER TYPE test_type DROP ATTRIBUTE c; -- fails +ERROR: column "c" of relation "test_type" does not exist +ALTER TYPE test_type DROP ATTRIBUTE IF EXISTS c; +NOTICE: column "c" of relation "test_type" does not exist, skipping +ALTER TYPE test_type DROP ATTRIBUTE a, ADD ATTRIBUTE d boolean; +\d test_type +Composite type "public.test_type" + Column | Type +--------+--------- + d | boolean + +ALTER TYPE test_type RENAME ATTRIBUTE a TO aa; +ERROR: column "a" does not exist +ALTER TYPE test_type RENAME ATTRIBUTE d TO dd; +\d test_type +Composite type "public.test_type" + Column | Type +--------+--------- + dd | boolean + +DROP TYPE test_type; +CREATE TYPE test_type1 AS (a int, b text); +CREATE TABLE test_tbl1 (x int, y test_type1); +ALTER TYPE test_type1 ALTER ATTRIBUTE b TYPE varchar; -- fails +ERROR: cannot alter type "test_type1" because column "test_tbl1"."y" uses it +CREATE TYPE test_type2 AS (a int, b text); +CREATE TABLE test_tbl2 OF test_type2; +ALTER TYPE test_type2 ADD ATTRIBUTE c text; -- fails +ERROR: cannot alter type "test_type2" because it is the type of a typed table +ALTER TYPE test_type2 ALTER ATTRIBUTE b TYPE varchar; -- fails +ERROR: cannot alter type "test_type2" because it is the type of a typed table +ALTER TYPE test_type2 DROP ATTRIBUTE b; -- fails +ERROR: cannot alter type "test_type2" because it is the type of a typed table +ALTER TYPE test_type2 RENAME ATTRIBUTE b TO bb; -- fails +ERROR: cannot alter type "test_type2" because it is the type of a typed table +CREATE TYPE test_type_empty AS (); +DROP TYPE test_type_empty; diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index 760670cd06..3e1646a96d 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -1090,6 +1090,11 @@ select * from another; drop table another; +-- table's row type +create table tab1 (a int, b text); +create table tab2 (x int, y tab1); +alter table tab1 alter column b type varchar; -- fails + -- -- lock levels -- @@ -1224,3 +1229,53 @@ select alter2.plus1(41); -- clean up drop schema alter2 cascade; + +-- +-- composite types +-- + +CREATE TYPE test_type AS (a int); +\d test_type + +ALTER TYPE nosuchtype ADD ATTRIBUTE b text; -- fails + +ALTER TYPE test_type ADD ATTRIBUTE b text; +\d test_type + +ALTER TYPE test_type ADD ATTRIBUTE b text; -- fails + +ALTER TYPE test_type ALTER ATTRIBUTE b SET DATA TYPE varchar; +\d test_type + +ALTER TYPE test_type ALTER ATTRIBUTE b SET DATA TYPE integer; +\d test_type + +ALTER TYPE test_type DROP ATTRIBUTE b; +\d test_type + +ALTER TYPE test_type DROP ATTRIBUTE c; -- fails + +ALTER TYPE test_type DROP ATTRIBUTE IF EXISTS c; + +ALTER TYPE test_type DROP ATTRIBUTE a, ADD ATTRIBUTE d boolean; +\d test_type + +ALTER TYPE test_type RENAME ATTRIBUTE a TO aa; +ALTER TYPE test_type RENAME ATTRIBUTE d TO dd; +\d test_type + +DROP TYPE test_type; + +CREATE TYPE test_type1 AS (a int, b text); +CREATE TABLE test_tbl1 (x int, y test_type1); +ALTER TYPE test_type1 ALTER ATTRIBUTE b TYPE varchar; -- fails + +CREATE TYPE test_type2 AS (a int, b text); +CREATE TABLE test_tbl2 OF test_type2; +ALTER TYPE test_type2 ADD ATTRIBUTE c text; -- fails +ALTER TYPE test_type2 ALTER ATTRIBUTE b TYPE varchar; -- fails +ALTER TYPE test_type2 DROP ATTRIBUTE b; -- fails +ALTER TYPE test_type2 RENAME ATTRIBUTE b TO bb; -- fails + +CREATE TYPE test_type_empty AS (); +DROP TYPE test_type_empty;