From 853153ca6dfdf2bdf6ce800216de75b2fefbb4f1 Mon Sep 17 00:00:00 2001 From: Bruce Momjian Date: Fri, 6 Dec 2002 03:28:34 +0000 Subject: [PATCH] ALTER DOMAIN .. SET / DROP NOT NULL ALTER DOMAIN .. SET / DROP DEFAULT ALTER DOMAIN .. ADD / DROP CONSTRAINT New files: - doc/src/sgml/ref/alter_domain.sgml Rod Taylor --- doc/src/sgml/ref/allfiles.sgml | 3 +- doc/src/sgml/ref/alter_domain.sgml | 237 +++++++++ doc/src/sgml/reference.sgml | 3 +- src/backend/catalog/dependency.c | 3 +- src/backend/catalog/pg_constraint.c | 15 +- src/backend/catalog/pg_type.c | 184 ++++--- src/backend/commands/cluster.c | 10 +- src/backend/commands/typecmds.c | 966 +++++++++++++++++++++++++++++------ src/backend/executor/execQual.c | 6 +- src/backend/nodes/copyfuncs.c | 19 +- src/backend/nodes/equalfuncs.c | 17 +- src/backend/parser/gram.y | 52 +- src/backend/parser/parse_expr.c | 10 +- src/backend/tcop/postgres.c | 8 +- src/backend/tcop/utility.c | 46 +- src/include/catalog/pg_type.h | 16 +- src/include/commands/defrem.h | 10 +- src/include/nodes/nodes.h | 3 +- src/include/nodes/parsenodes.h | 29 +- src/test/regress/expected/domain.out | 80 ++- src/test/regress/resultmap | 4 +- src/test/regress/sql/domain.sql | 67 ++- 22 files changed, 1526 insertions(+), 262 deletions(-) create mode 100644 doc/src/sgml/ref/alter_domain.sgml diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index bd8c3e71f1..9a06b19d4e 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -1,5 +1,5 @@ @@ -38,6 +38,7 @@ Complete list of usable sgml source files in this directory. + diff --git a/doc/src/sgml/ref/alter_domain.sgml b/doc/src/sgml/ref/alter_domain.sgml new file mode 100644 index 0000000000..8c26e01706 --- /dev/null +++ b/doc/src/sgml/ref/alter_domain.sgml @@ -0,0 +1,237 @@ + + + + + ALTER DOMAIN + SQL - Language Statements + + + + ALTER DOMAIN + + + change the definition of a table + + + + + 2002-11-27 + + +ALTER DOMAIN domain + { SET DEFAULT value | DROP DEFAULT } +ALTER DOMAIN domain + { SET | DROP } NOT NULL +ALTER DOMAIN domain + ADD domain_constraint +ALTER DOMAIN domain + DROP CONSTRAINT constraint_name [ RESTRICT | CASCADE ] + + + + + 2002-11-27 + + + Inputs + + + + + + domain + + + The name (possibly schema-qualified) of an existing domain to + alter. + + + + + + domain_constraint + + + New domain constraint for the domain. + + + + + + constraint_name + + + Name of an existing constraint to drop. + + + + + + CASCADE + + + Automatically drop objects that depend constraint. + + + + + + RESTRICT + + + Refuse to drop the constraint if there are any dependent + objects. This is the default behavior. + + + + + + + + + + + 1998-04-15 + + + Outputs + + + + + + ALTER DOMAIN + + + Message returned from domain alteration. + + + + + + ERROR + + + Message returned if domain is not available. + + + + + + + + + + + 2002-11-27 + + + Description + + + ALTER DOMAIN changes the definition of an existing domain. + There are several sub-forms: + + + + + SET/DROP DEFAULT + + + These forms set or remove the default value for a column. Note + that defaults only apply to subsequent INSERT + commands; they do not cause rows already in a table using the domain. + + + + + + SET/DROP NOT NULL + + + These forms change whether a domain is marked to allow NULL + values or to reject NULL values. You may only SET NOT NULL + when the tables using the domain contain no null values in the domain + based column. + + + + + + ADD domain_constraint + + + This form adds a new constraint to a table using the same syntax as + . + + + + + + DROP CONSTRAINT + + + This form drops constraints on a domain. + + + + + + + + You must own the domain to use ALTER DOMAIN; except for + ALTER TABLE OWNER, which may only be executed by a superuser. + + + + + + Usage + + + To add a NOT NULL constraint to a column: + +ALTER DOMAIN zipcode SET NOT NULL; + + To remove a NOT NULL constraint from a column: + +ALTER DOMAIN zipcode DROP NOT NULL; + + + + + To add a check constraint to a table: + +ALTER DOMAIN zipcode ADD CONSTRAINT zipchk CHECK (char_length(VALUE) = 5); + + + + + To remove a check constraint from a table and all its children: + +ALTER DOMAIN zipcode DROP CONSTRAINT zipchk; + + + + + + + diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index 247ce8d8ce..8709343c3c 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -1,5 +1,5 @@ @@ -47,6 +47,7 @@ PostgreSQL Reference Manual &abort; &alterDatabase; + &alterDomain; &alterGroup; &alterTable; &alterTrigger; diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index c679c61e12..3fb609b3f1 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -8,7 +8,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/catalog/dependency.c,v 1.14 2002/12/04 20:00:37 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/dependency.c,v 1.15 2002/12/06 03:28:27 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -36,6 +36,7 @@ #include "commands/proclang.h" #include "commands/schemacmds.h" #include "commands/trigger.h" +#include "commands/typecmds.h" #include "lib/stringinfo.h" #include "miscadmin.h" #include "optimizer/clauses.h" diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 7cd105928c..d4e06a17e0 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/catalog/pg_constraint.c,v 1.8 2002/11/15 02:50:05 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/pg_constraint.c,v 1.9 2002/12/06 03:28:27 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -199,6 +199,7 @@ CreateConstraintEntry(const char *constraintName, domobject.classId = RelOid_pg_type; domobject.objectId = domainId; + domobject.objectSubId = 0; recordDependencyOn(&conobject, &domobject, DEPENDENCY_AUTO); } @@ -496,15 +497,16 @@ RemoveConstraintById(Oid conId) HeapTuple typTup; ScanKeyData typKey[1]; SysScanDesc typScan; + int nkeys = 0; typRel = heap_openr(TypeRelationName, RowExclusiveLock); - ScanKeyEntryInitialize(&typKey[0], 0x0, - Anum_pg_constraint_contypid, F_OIDEQ, + ScanKeyEntryInitialize(&typKey[nkeys++], 0x0, + ObjectIdAttributeNumber, F_OIDEQ, ObjectIdGetDatum(con->contypid)); typScan = systable_beginscan(typRel, TypeOidIndex, true, - SnapshotNow, 1, typKey); + SnapshotNow, nkeys, typKey); typTup = systable_getnext(typScan); @@ -517,6 +519,11 @@ RemoveConstraintById(Oid conId) /* Keep lock on domain type until end of xact */ heap_close(typRel, NoLock); } + else + { + elog(ERROR, "RemoveConstraintById: Constraint %u is not a known type", + conId); + } /* Fry the constraint itself */ simple_heap_delete(conDesc, &tup->t_self); diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c index d43c1edeee..25721ebd42 100644 --- a/src/backend/catalog/pg_type.c +++ b/src/backend/catalog/pg_type.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/catalog/pg_type.c,v 1.82 2002/09/04 20:31:14 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/pg_type.c,v 1.83 2002/12/06 03:28:27 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -139,7 +139,7 @@ TypeCreate(const char *typeName, Oid elementType, Oid baseType, const char *defaultTypeValue, /* human readable rep */ - const char *defaultTypeBin, /* cooked rep */ + char *defaultTypeBin, /* cooked rep */ bool passedByValue, char alignment, char storage, @@ -291,87 +291,125 @@ TypeCreate(const char *typeName, */ if (!IsBootstrapProcessingMode()) { - ObjectAddress myself, - referenced; - - myself.classId = RelOid_pg_type; - myself.objectId = typeObjectId; - myself.objectSubId = 0; - - /* dependency on namespace */ - /* skip for relation rowtype, since we have indirect dependency */ - if (!OidIsValid(relationOid)) - { - referenced.classId = get_system_catalog_relid(NamespaceRelationName); - referenced.objectId = typeNamespace; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); - } - - /* Normal dependencies on the I/O functions */ - referenced.classId = RelOid_pg_proc; - referenced.objectId = inputProcedure; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + GenerateTypeDependencies(typeNamespace, + typeObjectId, + relationOid, + relationKind, + inputProcedure, + outputProcedure, + elementType, + baseType, + defaultTypeBin, + false); + } + + /* + * finish up + */ + heap_close(pg_type_desc, RowExclusiveLock); + + return typeObjectId; +} - referenced.classId = RelOid_pg_proc; - referenced.objectId = outputProcedure; +void +GenerateTypeDependencies(Oid typeNamespace, + Oid typeObjectId, + Oid relationOid, /* only for 'c'atalog typeType */ + char relationKind, + Oid inputProcedure, + Oid outputProcedure, + Oid elementType, + Oid baseType, + char *defaultTypeBin, /* cooked rep */ + bool rebuild) +{ + ObjectAddress myself, + referenced; + + /* + * If true, we need to remove all current dependencies that the type + * holds, and rebuild them from scratch. This allows us to easily + * implement alter type, and alter domain statements. + */ + if (rebuild) + deleteDependencyRecordsFor(RelOid_pg_type, + typeObjectId); + + myself.classId = RelOid_pg_type; + myself.objectId = typeObjectId; + myself.objectSubId = 0; + + /* dependency on namespace */ + /* skip for relation rowtype, since we have indirect dependency */ + if (!OidIsValid(relationOid)) + { + referenced.classId = get_system_catalog_relid(NamespaceRelationName); + referenced.objectId = typeNamespace; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } - /* - * If the type is a rowtype for a relation, mark it as internally - * dependent on the relation, *unless* it is a stand-alone - * composite type relation. For the latter case, we have to - * reverse the dependency. - * - * In the former case, this allows the type to be auto-dropped when - * the relation is, and not otherwise. And in the latter, of - * course we get the opposite effect. - */ - if (OidIsValid(relationOid)) - { - referenced.classId = RelOid_pg_class; - referenced.objectId = relationOid; - referenced.objectSubId = 0; - - if (relationKind != RELKIND_COMPOSITE_TYPE) - recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL); - else - recordDependencyOn(&referenced, &myself, DEPENDENCY_INTERNAL); - } + /* Normal dependencies on the I/O functions */ + referenced.classId = RelOid_pg_proc; + referenced.objectId = inputProcedure; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); - /* - * If the type is an array type, mark it auto-dependent on the - * base type. (This is a compromise between the typical case - * where the array type is automatically generated and the case - * where it is manually created: we'd prefer INTERNAL for the - * former case and NORMAL for the latter.) - */ - if (OidIsValid(elementType)) - { - referenced.classId = RelOid_pg_type; - referenced.objectId = elementType; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); - } - - /* Normal dependency from a domain to its base type. */ - if (OidIsValid(baseType)) - { - referenced.classId = RelOid_pg_type; - referenced.objectId = baseType; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); - } + referenced.classId = RelOid_pg_proc; + referenced.objectId = outputProcedure; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + /* + * If the type is a rowtype for a relation, mark it as internally + * dependent on the relation, *unless* it is a stand-alone + * composite type relation. For the latter case, we have to + * reverse the dependency. + * + * In the former case, this allows the type to be auto-dropped when + * the relation is, and not otherwise. And in the latter, of + * course we get the opposite effect. + */ + if (OidIsValid(relationOid)) + { + referenced.classId = RelOid_pg_class; + referenced.objectId = relationOid; + referenced.objectSubId = 0; + + if (relationKind != RELKIND_COMPOSITE_TYPE) + recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL); + else + recordDependencyOn(&referenced, &myself, DEPENDENCY_INTERNAL); } /* - * finish up + * If the type is an array type, mark it auto-dependent on the + * base type. (This is a compromise between the typical case + * where the array type is automatically generated and the case + * where it is manually created: we'd prefer INTERNAL for the + * former case and NORMAL for the latter.) */ - heap_close(pg_type_desc, RowExclusiveLock); + if (OidIsValid(elementType)) + { + referenced.classId = RelOid_pg_type; + referenced.objectId = elementType; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); + } - return typeObjectId; + /* Normal dependency from a domain to its base type. */ + if (OidIsValid(baseType)) + { + referenced.classId = RelOid_pg_type; + referenced.objectId = baseType; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } + + /* Dependency on the default expression */ + if (defaultTypeBin) + recordDependencyOnExpr(&myself, stringToNode(defaultTypeBin), + NIL, DEPENDENCY_NORMAL); } /* diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 3009059549..4822ae4160 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/cluster.c,v 1.99 2002/12/05 04:04:42 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/cluster.c,v 1.100 2002/12/06 03:28:27 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -667,7 +667,7 @@ cluster(ClusterStmt *stmt) tableOid = RangeVarGetRelid(stmt->relation, false); if (!check_cluster_ownership(tableOid)) elog(ERROR, "CLUSTER: You do not own relation %s", - stmt->relation->relname); + stmt->relation->relname); tuple = SearchSysCache(RELOID, ObjectIdGetDatum(tableOid), @@ -706,7 +706,7 @@ cluster(ClusterStmt *stmt) } if (indexOid == InvalidOid) elog(ERROR, "CLUSTER: No previously clustered index found on table %s", - stmt->relation->relname); + stmt->relation->relname); RelationClose(rel); ReleaseSysCache(ituple); ReleaseSysCache(idxtuple); @@ -721,7 +721,7 @@ cluster(ClusterStmt *stmt) /* XXX Maybe the namespace should be reported as well */ if (!OidIsValid(indexOid)) elog(ERROR, "CLUSTER: cannot find index \"%s\" for table \"%s\"", - stmt->indexname, stmt->relation->relname); + stmt->indexname, stmt->relation->relname); rvtc.tableOid = tableOid; rvtc.indexOid = indexOid; rvtc.isPrevious = false; @@ -819,7 +819,7 @@ get_tables_to_cluster(AclId owner) List *rvs = NIL; /* - * Get all indexes that have indisclustered set. System + * Get all indexes that have indisclustered set. System * relations or nailed-in relations cannot ever have * indisclustered set, because CLUSTER will refuse to * set it when called with one of them as argument. diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index ab0608a08a..ebae0e272d 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/typecmds.c,v 1.17 2002/11/15 02:50:06 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/typecmds.c,v 1.18 2002/12/06 03:28:27 momjian Exp $ * * DESCRIPTION * The "DefineFoo" routines take the parse tree and pick out the @@ -32,14 +32,18 @@ #include "postgres.h" #include "access/heapam.h" +#include "access/genam.h" #include "catalog/catname.h" #include "catalog/dependency.h" #include "catalog/heap.h" +#include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/pg_constraint.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "commands/tablecmds.h" +#include "commands/typecmds.h" +#include "executor/executor.h" #include "miscadmin.h" #include "nodes/nodes.h" #include "optimizer/clauses.h" @@ -48,6 +52,7 @@ #include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "parser/parse_func.h" +#include "parser/parse_relation.h" #include "parser/parse_type.h" #include "utils/acl.h" #include "utils/builtins.h" @@ -55,8 +60,21 @@ #include "utils/lsyscache.h" #include "utils/syscache.h" - static Oid findTypeIOFunction(List *procname, Oid typeOid, bool isOutput); +static List *get_rels_with_domain(Oid domainOid); +static void domainPermissionCheck(HeapTuple tup, TypeName *typename); +static void domainCheckForUnsupportedConstraints(Node *newConstraint); +static char *domainAddConstraint(Oid domainOid, Oid domainNamespace, + Oid baseTypeOid, + int typMod, Constraint *constr, + int *counter, char *domainName); + +typedef struct +{ + Oid relOid; + int natts; + int *atts; +} relToCheck; /* * DefineType @@ -501,12 +519,14 @@ DefineDomain(CreateDomainStmt *stmt) Constraint *colDef; ParseState *pstate; - /* Prior to processing, confirm that it is not a foreign key constraint */ - if (nodeTag(newConstraint) == T_FkConstraint) - elog(ERROR, "CREATE DOMAIN / FOREIGN KEY constraints not supported"); + /* + * Check for constraint types which are not supported by + * domains. Throws an error if it finds one. + */ + domainCheckForUnsupportedConstraints(newConstraint); + /* Assume its a CHECK, DEFAULT, NULL or NOT NULL constraint */ colDef = (Constraint *) newConstraint; - switch (colDef->contype) { /* @@ -560,25 +580,17 @@ DefineDomain(CreateDomainStmt *stmt) nullDefined = true; break; - case CONSTR_UNIQUE: - elog(ERROR, "CREATE DOMAIN / UNIQUE indexes not supported"); - break; - - case CONSTR_PRIMARY: - elog(ERROR, "CREATE DOMAIN / PRIMARY KEY indexes not supported"); - break; - - /* Check constraints are handled after domain creation */ + /* + * Check constraints are handled after domain creation, as they require + * the Oid of the domain + */ case CONSTR_CHECK: break; - case CONSTR_ATTR_DEFERRABLE: - case CONSTR_ATTR_NOT_DEFERRABLE: - case CONSTR_ATTR_DEFERRED: - case CONSTR_ATTR_IMMEDIATE: - elog(ERROR, "DefineDomain: DEFERRABLE, NON DEFERRABLE, DEFERRED and IMMEDIATE not supported"); - break; - + /* + * If we reach this, then domainCheckForUnsupportedConstraints() + * doesn't have a complete list of unsupported domain constraints + */ default: elog(ERROR, "DefineDomain: unrecognized constraint node type"); break; @@ -616,143 +628,27 @@ DefineDomain(CreateDomainStmt *stmt) foreach(listptr, schema) { Constraint *constr = lfirst(listptr); - ParseState *pstate; switch (constr->contype) { case CONSTR_CHECK: { - Node *expr; - char *ccsrc; - char *ccbin; - ConstraintTestValue *domVal; - - /* - * Assign or validate constraint name - */ - if (constr->name) - { - if (ConstraintNameIsUsed(CONSTRAINT_DOMAIN, - domainoid, - domainNamespace, - constr->name)) - elog(ERROR, "constraint \"%s\" already exists for domain \"%s\"", - constr->name, - domainName); - } - else - constr->name = GenerateConstraintName(CONSTRAINT_DOMAIN, - domainoid, - domainNamespace, - &counter); - - /* - * Convert the A_EXPR in raw_expr into an - * EXPR - */ - pstate = make_parsestate(NULL); - - /* - * We want to have the domain VALUE node type filled in so - * that proper casting can occur. - */ - domVal = makeNode(ConstraintTestValue); - domVal->typeId = basetypeoid; - domVal->typeMod = stmt->typename->typmod; - - expr = transformExpr(pstate, constr->raw_expr, domVal); - - /* - * Domains don't allow var clauses - */ - if (contain_var_clause(expr)) - elog(ERROR, "cannot use column references in domain CHECK clause"); - - /* - * Make sure it yields a boolean result. - */ - expr = coerce_to_boolean(expr, "CHECK"); - - /* - * Make sure no outside relations are - * referred to. - */ - if (length(pstate->p_rtable) != 0) - elog(ERROR, "Relations cannot be referenced in domain CHECK constraint"); - - /* - * No subplans or aggregates, either... - */ - if (contain_subplans(expr)) - elog(ERROR, "cannot use subselect in CHECK constraint expression"); - if (contain_agg_clause(expr)) - elog(ERROR, "cannot use aggregate function in CHECK constraint expression"); - - /* - * Might as well try to reduce any constant expressions. - */ - expr = eval_const_expressions(expr); - - /* - * Must fix opids in operator clauses. - */ - fix_opids(expr); - - ccbin = nodeToString(expr); - - /* - * Deparse it. Since VARNOs aren't allowed in domain - * constraints, relation context isn't required as anything - * other than a shell. - */ - ccsrc = deparse_expression(expr, - deparse_context_for(domainName, - InvalidOid), - false, false); - - /* Write the constraint */ - CreateConstraintEntry(constr->name, /* Constraint Name */ - domainNamespace, /* namespace */ - CONSTRAINT_CHECK, /* Constraint Type */ - false, /* Is Deferrable */ - false, /* Is Deferred */ - InvalidOid, /* not a relation constraint */ - NULL, - 0, - domainoid, /* domain constraint */ - InvalidOid, /* Foreign key fields */ - NULL, - 0, - ' ', - ' ', - ' ', - InvalidOid, - expr, /* Tree form check constraint */ - ccbin, /* Binary form check constraint */ - ccsrc); /* Source form check constraint */ + char *junk; + + /* Returns the cooked constraint which is not needed during creation */ + junk = domainAddConstraint(domainoid, domainNamespace, + basetypeoid, stmt->typename->typmod, + constr, &counter, domainName); } break; + + /* Errors for other constraints are taken care of prior to domain creation */ default: break; } } /* - * Add any dependencies needed for the default expression. - */ - if (defaultExpr) - { - ObjectAddress domobject; - - domobject.classId = RelOid_pg_type; - domobject.objectId = domainoid; - domobject.objectSubId = 0; - - recordDependencyOnExpr(&domobject, defaultExpr, NIL, - DEPENDENCY_NORMAL); - } - - /* * Now we can clean up. */ ReleaseSysCache(typeTup); @@ -931,7 +827,8 @@ findTypeIOFunction(List *procname, Oid typeOid, bool isOutput) if (OidIsValid(procOid)) { /* Found, but must complain and fix the pg_proc entry */ - elog(NOTICE, "TypeCreate: changing argument type of function %s from OPAQUE to CSTRING", + elog(NOTICE, "TypeCreate: changing argument type of function %s " + "from OPAQUE to CSTRING", NameListToString(procname)); SetFunctionArgType(procOid, 0, CSTRINGOID); /* @@ -992,3 +889,778 @@ DefineCompositeType(const RangeVar *typevar, List *coldeflist) */ return DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE); } + +/* + * AlterDomainDefault + * + * Routine implementing ALTER DOMAIN SET/DROP DEFAULT statements. + */ +void +AlterDomainDefault(List *names, Node *defaultRaw) +{ + TypeName *typename; + Oid domainoid; + HeapTuple tup; + ParseState *pstate; + Relation rel; + char *defaultValue; + Node *defaultExpr = NULL; /* NULL if no default specified */ + Datum new_record[Natts_pg_type]; + char new_record_nulls[Natts_pg_type]; + char new_record_repl[Natts_pg_type]; + HeapTuple newtuple; + Form_pg_type typTup; + + /* Make a TypeName so we can use standard type lookup machinery */ + typename = makeNode(TypeName); + typename->names = names; + typename->typmod = -1; + typename->arrayBounds = NIL; + + /* Lock the domain in the type table */ + rel = heap_openr(TypeRelationName, RowExclusiveLock); + + /* Use LookupTypeName here so that shell types can be removed. */ + domainoid = LookupTypeName(typename); + if (!OidIsValid(domainoid)) + elog(ERROR, "Type \"%s\" does not exist", + TypeNameToString(typename)); + + tup = SearchSysCacheCopy(TYPEOID, + ObjectIdGetDatum(domainoid), + 0, 0, 0); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "AlterDomain: type \"%s\" does not exist", + TypeNameToString(typename)); + + /* Doesn't return if user isn't allowed to alter the domain */ + domainPermissionCheck(tup, typename); + + /* Setup new tuple */ + MemSet(new_record, (Datum) 0, sizeof(new_record)); + MemSet(new_record_nulls, ' ', sizeof(new_record_nulls)); + MemSet(new_record_repl, ' ', sizeof(new_record_repl)); + + /* Useful later */ + typTup = (Form_pg_type) GETSTRUCT(tup); + + /* Store the new default, if null then skip this step */ + if (defaultRaw) + { + /* Create a dummy ParseState for transformExpr */ + pstate = make_parsestate(NULL); + /* + * Cook the colDef->raw_expr into an expression. Note: + * Name is strictly for error message + */ + defaultExpr = cookDefault(pstate, defaultRaw, + typTup->typbasetype, + typTup->typtypmod, + NameStr(typTup->typname)); + + /* + * Expression must be stored as a nodeToString result, but + * we also require a valid textual representation (mainly + * to make life easier for pg_dump). + */ + defaultValue = deparse_expression(defaultExpr, + deparse_context_for(NameStr(typTup->typname), + InvalidOid), + false, false); + /* + * Form an updated tuple with the new default and write it back. + */ + new_record[Anum_pg_type_typdefaultbin - 1] = DirectFunctionCall1(textin, + CStringGetDatum( + nodeToString(defaultExpr))); + + new_record_repl[Anum_pg_type_typdefaultbin - 1] = 'r'; + new_record[Anum_pg_type_typdefault - 1] = DirectFunctionCall1(textin, + CStringGetDatum(defaultValue)); + new_record_repl[Anum_pg_type_typdefault - 1] = 'r'; + } + else /* Default is NULL, drop it */ + { + new_record_nulls[Anum_pg_type_typdefaultbin - 1] = 'n'; + new_record_repl[Anum_pg_type_typdefaultbin - 1] = 'r'; + new_record_nulls[Anum_pg_type_typdefault - 1] = 'n'; + new_record_repl[Anum_pg_type_typdefault - 1] = 'r'; + } + + newtuple = heap_modifytuple(tup, rel, + new_record, new_record_nulls, new_record_repl); + + simple_heap_update(rel, &tup->t_self, newtuple); + + CatalogUpdateIndexes(rel, newtuple); + + /* Rebuild dependencies */ + GenerateTypeDependencies(typTup->typnamespace, + domainoid, + typTup->typrelid, + InvalidOid, + typTup->typinput, + typTup->typoutput, + typTup->typelem, + typTup->typbasetype, + nodeToString(defaultExpr), + true); /* Rebuild is true */ + + /* Clean up */ + heap_close(rel, NoLock); + heap_freetuple(newtuple); +}; + +/* + * AlterDomainNotNull + * + * Routine implementing ALTER DOMAIN SET/DROP NOT NULL statements. + */ +void +AlterDomainNotNull(List *names, bool notNull) +{ + TypeName *typename; + Oid domainoid; + HeapTuple tup; + Relation rel; + Datum new_record[Natts_pg_type]; + char new_record_nulls[Natts_pg_type]; + char new_record_repl[Natts_pg_type]; + HeapTuple newtuple; + Form_pg_type typTup; + + /* Make a TypeName so we can use standard type lookup machinery */ + typename = makeNode(TypeName); + typename->names = names; + typename->typmod = -1; + typename->arrayBounds = NIL; + + /* Lock the type table */ + rel = heap_openr(TypeRelationName, RowExclusiveLock); + + /* Use LookupTypeName here so that shell types can be removed. */ + domainoid = LookupTypeName(typename); + if (!OidIsValid(domainoid)) + elog(ERROR, "Type \"%s\" does not exist", + TypeNameToString(typename)); + + tup = SearchSysCacheCopy(TYPEOID, + ObjectIdGetDatum(domainoid), + 0, 0, 0); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "AlterDomain: type \"%s\" does not exist", + TypeNameToString(typename)); + + /* Doesn't return if user isn't allowed to alter the domain */ + domainPermissionCheck(tup, typename); + + typTup = (Form_pg_type) GETSTRUCT(tup); + + /* Is the domain already set to the destination constraint? */ + if (typTup->typnotnull == notNull) + elog(ERROR, "AlterDomain: %s is already set to %s", + TypeNameToString(typename), + notNull ? "NOT NULL" : "NULL"); + + /* Adding a NOT NULL constraint requires checking current domains */ + if (notNull) + { + List *rels; + List *rt; + + /* Fetch relation list with attributes based on this domain */ + rels = get_rels_with_domain(domainoid); + + foreach (rt, rels) + { + Relation typrel; + HeapTuple tuple; + HeapScanDesc scan; + TupleDesc tupdesc; + relToCheck *rtc = (relToCheck *) lfirst(rt); + + /* Lock relation */ + typrel = heap_open(rtc->relOid, ExclusiveLock); + + tupdesc = RelationGetDescr(typrel); + + /* Fetch tuples sequentially */ + scan = heap_beginscan(typrel, SnapshotNow, 0, NULL); + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + int i; + + /* Test attributes */ + for (i = 0; i < rtc->natts; i++) + { + Datum d; + bool isNull; + + d = heap_getattr(tuple, rtc->atts[i], tupdesc, &isNull); + + if (isNull) + elog(ERROR, "ALTER DOMAIN: Relation \"%s\" Attribute \"%s\" " + "contains NULL values", + RelationGetRelationName(typrel), + NameStr(*attnumAttName(typrel, rtc->atts[i]))); + } + } + + heap_endscan(scan); + + /* Release lock */ + heap_close(typrel, NoLock); + } + } + + + /* Setup new tuple */ + MemSet(new_record, (Datum) 0, sizeof(new_record)); + MemSet(new_record_nulls, ' ', sizeof(new_record_nulls)); + MemSet(new_record_repl, ' ', sizeof(new_record_repl)); + + new_record[Anum_pg_type_typnotnull - 1] = BoolGetDatum(notNull); + new_record_repl[Anum_pg_type_typnotnull - 1] = 'r'; + + /* Build the new tuple */ + newtuple = heap_modifytuple(tup, rel, + new_record, new_record_nulls, new_record_repl); + + simple_heap_update(rel, &tup->t_self, newtuple); + + CatalogUpdateIndexes(rel, newtuple); + + /* Clean up */ + heap_close(rel, NoLock); + heap_freetuple(newtuple); +} + +/* + * AlterDomainDropConstraint + * + * Implements the ALTER DOMAIN DROP CONSTRAINT statement + */ +void +AlterDomainDropConstraint(List *names, const char *constrName, DropBehavior behavior) +{ + TypeName *typename; + Oid domainoid; + HeapTuple tup; + Relation rel; + Form_pg_type typTup; + Relation conrel; + SysScanDesc conscan; + ScanKeyData key[1]; + HeapTuple contup; + + /* Make a TypeName so we can use standard type lookup machinery */ + typename = makeNode(TypeName); + typename->names = names; + typename->typmod = -1; + typename->arrayBounds = NIL; + + /* Lock the type table */ + rel = heap_openr(TypeRelationName, RowExclusiveLock); + + /* Use LookupTypeName here so that shell types can be removed. */ + domainoid = LookupTypeName(typename); + if (!OidIsValid(domainoid)) + elog(ERROR, "Type \"%s\" does not exist", + TypeNameToString(typename)); + + tup = SearchSysCacheCopy(TYPEOID, + ObjectIdGetDatum(domainoid), + 0, 0, 0); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "AlterDomain: type \"%s\" does not exist", + TypeNameToString(typename)); + + /* Doesn't return if user isn't allowed to alter the domain */ + domainPermissionCheck(tup, typename); + + /* Grab an appropriate lock on the pg_constraint relation */ + conrel = heap_openr(ConstraintRelationName, RowExclusiveLock); + + /* Use the index to scan only constraints of the target relation */ + ScanKeyEntryInitialize(&key[0], 0x0, + Anum_pg_constraint_contypid, F_OIDEQ, + ObjectIdGetDatum(HeapTupleGetOid(tup))); + + conscan = systable_beginscan(conrel, ConstraintTypidIndex, true, + SnapshotNow, 1, key); + + typTup = (Form_pg_type) GETSTRUCT(tup); + + /* + * Scan over the result set, removing any matching entries. + */ + while ((contup = systable_getnext(conscan)) != NULL) + { + Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(contup); + + if (strcmp(NameStr(con->conname), constrName) == 0) + { + ObjectAddress conobj; + + conobj.classId = RelationGetRelid(conrel); + conobj.objectId = HeapTupleGetOid(contup); + conobj.objectSubId = 0; + + performDeletion(&conobj, behavior); + } + } + /* Clean up after the scan */ + systable_endscan(conscan); + heap_close(conrel, RowExclusiveLock); + + heap_close(rel, NoLock); +}; + +/* + * AlterDomainAddConstraint + * + * Implements the ALTER DOMAIN .. ADD CONSTRAINT statement. + */ +void +AlterDomainAddConstraint(List *names, Node *newConstraint) +{ + TypeName *typename; + Oid domainoid; + HeapTuple tup; + Relation rel; + List *rels; + List *rt; + Form_pg_type typTup; + char *ccbin; + Node *expr; + int counter = 0; + Constraint *constr; + + /* Make a TypeName so we can use standard type lookup machinery */ + typename = makeNode(TypeName); + typename->names = names; + typename->typmod = -1; + typename->arrayBounds = NIL; + + /* Lock the type table */ + rel = heap_openr(TypeRelationName, RowExclusiveLock); + + /* Use LookupTypeName here so that shell types can be removed. */ + domainoid = LookupTypeName(typename); + if (!OidIsValid(domainoid)) + elog(ERROR, "Type \"%s\" does not exist", + TypeNameToString(typename)); + + tup = SearchSysCacheCopy(TYPEOID, + ObjectIdGetDatum(domainoid), + 0, 0, 0); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "AlterDomain: type \"%s\" does not exist", + TypeNameToString(typename)); + + + /* Doesn't return if user isn't allowed to alter the domain */ + domainPermissionCheck(tup, typename); + + typTup = (Form_pg_type) GETSTRUCT(tup); + + + /* + * Check for constraint types which are not supported by + * domains. Throws an error if it finds one. + */ + domainCheckForUnsupportedConstraints(newConstraint); + + /* Assume its a CHECK, DEFAULT, NULL or NOT NULL constraint */ + constr = (Constraint *) newConstraint; + switch (constr->contype) + { + case CONSTR_DEFAULT: + elog(ERROR, "Use ALTER DOMAIN .. SET DEFAULT instead"); + break; + + case CONSTR_NOTNULL: + case CONSTR_NULL: + elog(ERROR, "Use ALTER DOMAIN .. [ SET | DROP ] NOT NULL instead"); + break; + + /* + * Check constraints are handled after domain creation, as they require + * the Oid of the domain + */ + case CONSTR_CHECK: + { + /* Returns the cooked constraint which is not needed during creation */ + ccbin = domainAddConstraint(HeapTupleGetOid(tup), typTup->typnamespace, + typTup->typbasetype, typTup->typtypmod, + constr, &counter, NameStr(typTup->typname)); + } + break; + + /* + * If we reach this, then domainCheckForUnsupportedConstraints() + * doesn't have a complete list of unsupported domain constraints + */ + default: + elog(ERROR, "DefineDomain: unrecognized constraint node type"); + break; + } + + /* + * Since all other constraint types throw errors, this must be + * a check constraint, and ccbin must be set. + * + * Test all values stored in the attributes based on the domain + * the constraint is being added to. + */ + expr = stringToNode(ccbin); + rels = get_rels_with_domain(domainoid); + foreach (rt, rels) + { + Relation typrel; + HeapTuple tuple; + HeapScanDesc scan; + TupleDesc tupdesc; + ExprContext *econtext; + TupleTableSlot *slot; + relToCheck *rtc = (relToCheck *) lfirst(rt); + + /* Lock relation */ + typrel = heap_open(rtc->relOid, ExclusiveLock); + + /* Test attributes */ + tupdesc = RelationGetDescr(typrel); + + /* Make tuple slot to hold tuples */ + slot = MakeTupleTableSlot(); + ExecSetSlotDescriptor(slot, RelationGetDescr(typrel), false); + + /* Make an expression context for ExecQual */ + econtext = MakeExprContext(slot, CurrentMemoryContext); + + /* Scan through table */ + scan = heap_beginscan(typrel, SnapshotNow, 0, NULL); + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + int i; + + ExecStoreTuple(tuple, slot, InvalidBuffer, false); + + /* Loop through each attribute of the tuple with a domain */ + for (i = 0; i < rtc->natts; i++) + { + Datum d; + bool isNull; + Datum conResult; + ExprDoneCond isDone; + + d = heap_getattr(tuple, rtc->atts[i], tupdesc, &isNull); + + if (isNull) + elog(ERROR, "ALTER DOMAIN: Relation \"%s\" Attribute \"%s\" " + "contains NULL values", + RelationGetRelationName(typrel), + NameStr(*attnumAttName(typrel, rtc->atts[i]))); + + econtext->domainValue_datum = d; + econtext->domainValue_isNull = isNull; + + conResult = ExecEvalExpr(expr, econtext, &isNull, &isDone); + + if (!DatumGetBool(conResult)) + elog(ERROR, "AlterDomainAddConstraint: Domain %s constraint %s failed", + NameStr(typTup->typname), constr->name); + } + + ResetExprContext(econtext); + } + + heap_endscan(scan); + + FreeExprContext(econtext); + pfree(slot); + + /* Hold type lock */ + heap_close(typrel, NoLock); + } + + /* Clean up */ + heap_close(rel, NoLock); +} + +/* + * get_rels_with_domain + * + * Fetch all relations / attributes which are using the domain + * while maintaining a RowExclusiveLock on the pg_attribute + * entries. + * + * Generally used for retrieving a list of tests when adding + * new constraints to a domain. + */ +List * +get_rels_with_domain(Oid domainOid) +{ + Relation classRel; + HeapTuple classTup; + Relation attRel; + HeapScanDesc classScan; + List *rels = NIL; + + /* + * We need to lock the domain rows for the length of the transaction, + * but once all of the tables and the appropriate attributes are + * found we can relese the relation lock. + */ + classRel = relation_openr(RelationRelationName, ExclusiveLock); + attRel = relation_openr(AttributeRelationName, RowExclusiveLock); + + classScan = heap_beginscan(classRel, SnapshotNow, 0, NULL); + + /* Scan through pg_class for tables */ + while ((classTup = heap_getnext(classScan, ForwardScanDirection)) != NULL) + { + bool addToList = true; + int nkeys = 0; + HeapTuple attTup; + HeapScanDesc attScan; + ScanKeyData attKey[2]; + Form_pg_class pg_class; + + /* Get our pg_class struct */ + pg_class = (Form_pg_class) GETSTRUCT(classTup); + + /* Fetch attributes from pg_attribute for the relation of the type domainOid */ + ScanKeyEntryInitialize(&attKey[nkeys++], 0, Anum_pg_attribute_attrelid, + F_OIDEQ, ObjectIdGetDatum(HeapTupleGetOid(classTup))); + + ScanKeyEntryInitialize(&attKey[nkeys++], 0, Anum_pg_attribute_atttypid, + F_OIDEQ, ObjectIdGetDatum(domainOid)); + + /* Setup to scan pg_attribute */ + attScan = heap_beginscan(attRel, SnapshotNow, nkeys, attKey); + + /* Scan through pg_attribute for attributes based on the domain */ + while ((attTup = heap_getnext(attScan, ForwardScanDirection)) != NULL) + { + relToCheck *rtc; + + /* Make the list entries for the relation */ + if (addToList) + { + addToList = false; + + rtc = (relToCheck *)palloc(sizeof(relToCheck)); + rtc->atts = (int *)palloc(sizeof(int) * pg_class->relnatts); + rtc->relOid = HeapTupleGetOid(classTup); + rtc->natts = 0; + rels = lcons((void *)rtc, rels); + } + + /* Now add the attribute */ + rtc->atts[rtc->natts++] = ((Form_pg_attribute) GETSTRUCT(attTup))->attnum; + } + + heap_endscan(attScan); + } + + heap_endscan(classScan); + + /* Release pg_class, hold pg_attribute for further processing */ + relation_close(classRel, ExclusiveLock); + relation_close(attRel, NoLock); + + return rels; +} + +/* + * domainPermissionCheck + * + * Throw an error if the current user doesn't have permission to modify + * the domain in an ALTER DOMAIN statement, or if the type isn't actually + * a domain. + */ +void +domainPermissionCheck(HeapTuple tup, TypeName *typename) +{ + Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup); + + /* Permission check: must own type or its namespace */ + if (!pg_type_ownercheck(HeapTupleGetOid(tup), GetUserId()) && + !pg_namespace_ownercheck(typTup->typnamespace, + GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, TypeNameToString(typename)); + + /* Check that this is actually a domain */ + if (typTup->typtype != 'd') + elog(ERROR, "%s is not a domain", + TypeNameToString(typename)); +} + + +/* + * + */ +char * +domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid, + int typMod, Constraint *constr, int *counter, char *domainName) +{ + Node *expr; + char *ccsrc; + char *ccbin; + ParseState *pstate; + ConstraintTestValue *domVal; + + /* + * Assign or validate constraint name + */ + if (constr->name) + { + if (ConstraintNameIsUsed(CONSTRAINT_DOMAIN, + domainOid, + domainNamespace, + constr->name)) + elog(ERROR, "constraint \"%s\" already exists for domain \"%s\"", + constr->name, + domainName); + } + else + constr->name = GenerateConstraintName(CONSTRAINT_DOMAIN, + domainOid, + domainNamespace, + counter); + + /* + * Convert the A_EXPR in raw_expr into an + * EXPR + */ + pstate = make_parsestate(NULL); + + /* + * We want to have the domain VALUE node type filled in so + * that proper casting can occur. + */ + domVal = makeNode(ConstraintTestValue); + domVal->typeId = baseTypeOid; + domVal->typeMod = typMod; + + expr = transformExpr(pstate, constr->raw_expr, domVal); + + /* + * Domains don't allow var clauses + */ + if (contain_var_clause(expr)) + elog(ERROR, "cannot use column references in domain CHECK clause"); + + /* + * Make sure it yields a boolean result. + */ + expr = coerce_to_boolean(expr, "CHECK"); + + /* + * Make sure no outside relations are + * referred to. + */ + if (length(pstate->p_rtable) != 0) + elog(ERROR, "Relations cannot be referenced in domain CHECK constraint"); + + /* + * No subplans or aggregates, either... + */ + if (contain_subplans(expr)) + elog(ERROR, "cannot use subselect in CHECK constraint expression"); + if (contain_agg_clause(expr)) + elog(ERROR, "cannot use aggregate function in CHECK constraint expression"); + + /* + * Might as well try to reduce any constant expressions. + */ + expr = eval_const_expressions(expr); + + /* + * Must fix opids in operator clauses. + */ + fix_opids(expr); + + ccbin = nodeToString(expr); + + /* + * Deparse it. Since VARNOs aren't allowed in domain + * constraints, relation context isn't required as anything + * other than a shell. + */ + ccsrc = deparse_expression(expr, + deparse_context_for(domainName, + InvalidOid), + false, false); + + /* Write the constraint */ + CreateConstraintEntry(constr->name, /* Constraint Name */ + domainNamespace, /* namespace */ + CONSTRAINT_CHECK, /* Constraint Type */ + false, /* Is Deferrable */ + false, /* Is Deferred */ + InvalidOid, /* not a relation constraint */ + NULL, + 0, + domainOid, /* domain constraint */ + InvalidOid, /* Foreign key fields */ + NULL, + 0, + ' ', + ' ', + ' ', + InvalidOid, + expr, /* Tree form check constraint */ + ccbin, /* Binary form check constraint */ + ccsrc); /* Source form check constraint */ + + /* + * Return the constraint so the calling routine can perform any additional + * required tests. + */ + return ccbin; +} + +/* + * domainCheckForUnsupportedConstraints + * + * Throws an error on constraints that are unsupported by the + * domains. + */ +void +domainCheckForUnsupportedConstraints(Node *newConstraint) +{ + Constraint *colDef; + + if (nodeTag(newConstraint) == T_FkConstraint) + elog(ERROR, "CREATE DOMAIN / FOREIGN KEY constraints not supported"); + + colDef = (Constraint *) newConstraint; + + switch (colDef->contype) + { + case CONSTR_UNIQUE: + elog(ERROR, "CREATE DOMAIN / UNIQUE indexes not supported"); + break; + + case CONSTR_PRIMARY: + elog(ERROR, "CREATE DOMAIN / PRIMARY KEY indexes not supported"); + break; + + case CONSTR_ATTR_DEFERRABLE: + case CONSTR_ATTR_NOT_DEFERRABLE: + case CONSTR_ATTR_DEFERRED: + case CONSTR_ATTR_IMMEDIATE: + elog(ERROR, "DefineDomain: DEFERRABLE, NON DEFERRABLE, DEFERRED" + " and IMMEDIATE not supported"); + break; + + default: + break; + } +} diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index e7c724c66e..fd531aaa73 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.113 2002/12/05 15:50:31 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.114 2002/12/06 03:28:27 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -1610,8 +1610,8 @@ ExecEvalConstraintTest(ConstraintTest *constraint, ExprContext *econtext, conResult = ExecEvalExpr(constraint->check_expr, econtext, isNull, isDone); if (!DatumGetBool(conResult)) - elog(ERROR, "Domain %s constraint %s failed", - constraint->name, constraint->domname); + elog(ERROR, "ExecEvalConstraintTest: Domain %s constraint %s failed", + constraint->domname, constraint->name); } break; default: diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 1fe7efd163..01abacfa02 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 - * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.226 2002/12/05 15:50:34 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.227 2002/12/06 03:28:28 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -1479,6 +1479,20 @@ _copyAlterTableStmt(AlterTableStmt *from) return newnode; } +static AlterDomainStmt * +_copyAlterDomainStmt(AlterDomainStmt *from) +{ + AlterDomainStmt *newnode = makeNode(AlterDomainStmt); + + COPY_SCALAR_FIELD(subtype); + COPY_NODE_FIELD(typename); + COPY_STRING_FIELD(name); + COPY_NODE_FIELD(def); + COPY_SCALAR_FIELD(behavior); + + return newnode; +} + static GrantStmt * _copyGrantStmt(GrantStmt *from) { @@ -2464,6 +2478,9 @@ copyObject(void *from) case T_AlterTableStmt: retval = _copyAlterTableStmt(from); break; + case T_AlterDomainStmt: + retval = _copyAlterDomainStmt(from); + break; case T_GrantStmt: retval = _copyGrantStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index f2cae4c14f..3c5dc6c03f 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.171 2002/12/05 15:50:34 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.172 2002/12/06 03:28:32 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -526,6 +526,18 @@ _equalAlterTableStmt(AlterTableStmt *a, AlterTableStmt *b) } static bool +_equalAlterDomainStmt(AlterDomainStmt *a, AlterDomainStmt *b) +{ + COMPARE_SCALAR_FIELD(subtype); + COMPARE_NODE_FIELD(typename); + COMPARE_STRING_FIELD(name); + COMPARE_NODE_FIELD(def); + COMPARE_SCALAR_FIELD(behavior); + + return true; +} + +static bool _equalGrantStmt(GrantStmt *a, GrantStmt *b) { COMPARE_SCALAR_FIELD(is_grant); @@ -1621,6 +1633,9 @@ equal(void *a, void *b) case T_AlterTableStmt: retval = _equalAlterTableStmt(a, b); break; + case T_AlterDomainStmt: + retval = _equalAlterDomainStmt(a, b); + break; case T_GrantStmt: retval = _equalGrantStmt(a, b); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index d4c879f770..e1fad41b0e 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.383 2002/11/25 21:29:40 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.384 2002/12/06 03:28:33 momjian Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -128,7 +128,7 @@ static void doNegateFloat(Value *v); } %type stmt schema_stmt - AlterDatabaseSetStmt AlterGroupStmt + AlterDatabaseSetStmt AlterDomainStmt AlterGroupStmt AlterTableStmt AlterUserStmt AlterUserSetStmt AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt @@ -479,6 +479,7 @@ stmtmulti: stmtmulti ';' stmt stmt : AlterDatabaseSetStmt + | AlterDomainStmt | AlterGroupStmt | AlterTableStmt | AlterUserStmt @@ -3729,6 +3730,53 @@ CreateDomainStmt: } ; +AlterDomainStmt: + /* ALTER DOMAIN {SET DEFAULT |DROP DEFAULT} */ + ALTER DOMAIN_P any_name alter_column_default + { + AlterDomainStmt *n = makeNode(AlterDomainStmt); + n->subtype = 'T'; + n->typename = $3; + n->def = $4; + $$ = (Node *)n; + } + /* ALTER DOMAIN DROP NOT NULL */ + | ALTER DOMAIN_P any_name DROP NOT NULL_P + { + AlterDomainStmt *n = makeNode(AlterDomainStmt); + n->subtype = 'N'; + n->typename = $3; + $$ = (Node *)n; + } + /* ALTER DOMAIN SET NOT NULL */ + | ALTER DOMAIN_P any_name SET NOT NULL_P + { + AlterDomainStmt *n = makeNode(AlterDomainStmt); + n->subtype = 'O'; + n->typename = $3; + $$ = (Node *)n; + } + /* ALTER DOMAIN ADD CONSTRAINT ... */ + | ALTER DOMAIN_P any_name ADD TableConstraint + { + AlterDomainStmt *n = makeNode(AlterDomainStmt); + n->subtype = 'C'; + n->typename = $3; + n->def = $5; + $$ = (Node *)n; + } + /* ALTER DOMAIN DROP CONSTRAINT [RESTRICT|CASCADE] */ + | ALTER DOMAIN_P any_name DROP CONSTRAINT name opt_drop_behavior + { + AlterDomainStmt *n = makeNode(AlterDomainStmt); + n->subtype = 'X'; + n->typename = $3; + n->name = $6; + n->behavior = $7; + $$ = (Node *)n; + } + ; + opt_as: AS {} | /* EMPTY */ {} ; diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index b620c7116b..834cc9c82d 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.132 2002/11/30 21:25:05 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.133 2002/12/06 03:28:33 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -629,6 +629,14 @@ transformExpr(ParseState *pstate, Node *expr, ConstraintTestValue *domVal) case T_DomainConstraintValue: { + /* + * If domVal is NULL, we are not translating an expression that + * can use it + */ + if (domVal == NULL) + elog(ERROR, "VALUE is not allowed in expression for node %d", + nodeTag(expr)); + result = (Node *) copyObject(domVal); break; diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 457391b6a9..ac51c0cf34 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.310 2002/11/19 17:21:40 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.311 2002/12/06 03:28:33 momjian Exp $ * * NOTES * this is the "main" module of the postgres backend and @@ -1781,7 +1781,7 @@ PostgresMain(int argc, char *argv[], const char *username) if (!IsUnderPostmaster) { puts("\nPOSTGRES backend interactive interface "); - puts("$Revision: 1.310 $ $Date: 2002/11/19 17:21:40 $\n"); + puts("$Revision: 1.311 $ $Date: 2002/12/06 03:28:33 $\n"); } /* @@ -2319,6 +2319,10 @@ CreateCommandTag(Node *parsetree) tag = "ALTER TABLE"; break; + case T_AlterDomainStmt: + tag = "ALTER DOMAIN"; + break; + case T_GrantStmt: { GrantStmt *stmt = (GrantStmt *) parsetree; diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 493dbd7587..a45c449535 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.182 2002/11/15 03:09:38 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.183 2002/12/06 03:28:33 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -36,6 +36,7 @@ #include "commands/sequence.h" #include "commands/tablecmds.h" #include "commands/trigger.h" +#include "commands/typecmds.h" #include "commands/user.h" #include "commands/vacuum.h" #include "commands/view.h" @@ -560,6 +561,49 @@ ProcessUtility(Node *parsetree, } break; + case T_AlterDomainStmt: + { + AlterDomainStmt *stmt = (AlterDomainStmt *) parsetree; + + /* + * Some or all of these functions are recursive to cover + * inherited things, so permission checks are done there. + */ + switch (stmt->subtype) + { + case 'T': /* ALTER COLUMN DEFAULT */ + + /* + * Recursively alter column default for table and, + * if requested, for descendants + */ + AlterDomainDefault(stmt->typename, + stmt->def); + break; + case 'N': /* ALTER COLUMN DROP NOT NULL */ + AlterDomainNotNull(stmt->typename, + false); + break; + case 'O': /* ALTER COLUMN SET NOT NULL */ + AlterDomainNotNull(stmt->typename, + true); + break; + case 'C': /* ADD CONSTRAINT */ + AlterDomainAddConstraint(stmt->typename, + stmt->def); + break; + case 'X': /* DROP CONSTRAINT */ + AlterDomainDropConstraint(stmt->typename, + stmt->name, + stmt->behavior); + break; + default: /* oops */ + elog(ERROR, "T_AlterDomainStmt: unknown subtype"); + break; + } + } + break; + case T_GrantStmt: ExecuteGrantStmt((GrantStmt *) parsetree); diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index 06ac2f340a..8804e5fe60 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: pg_type.h,v 1.134 2002/09/24 21:26:44 tgl Exp $ + * $Id: pg_type.h,v 1.135 2002/12/06 03:28:33 momjian Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -550,7 +550,7 @@ extern Oid TypeCreate(const char *typeName, Oid elementType, Oid baseType, const char *defaultTypeValue, - const char *defaultTypeBin, + char *defaultTypeBin, bool passedByValue, char alignment, char storage, @@ -558,6 +558,18 @@ extern Oid TypeCreate(const char *typeName, int32 typNDims, bool typeNotNull); +extern void +GenerateTypeDependencies(Oid typeNamespace, + Oid typeObjectId, + Oid relationOid, /* only for 'c'atalog typeType */ + char relationKind, + Oid inputProcedure, + Oid outputProcedure, + Oid elementType, + Oid baseType, + char *defaultTypeBin, /* cooked rep */ + bool rebuild); + extern void TypeRename(const char *oldTypeName, Oid typeNamespace, const char *newTypeName); diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 401610af6a..d098277e79 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: defrem.h,v 1.46 2002/09/21 18:39:26 tgl Exp $ + * $Id: defrem.h,v 1.47 2002/12/06 03:28:33 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -52,14 +52,6 @@ extern void RemoveOperatorById(Oid operOid); extern void DefineAggregate(List *names, List *parameters); extern void RemoveAggregate(RemoveAggrStmt *stmt); -/* commands/typecmds.c */ -extern void DefineType(List *names, List *parameters); -extern void RemoveType(List *names, DropBehavior behavior); -extern void RemoveTypeById(Oid typeOid); -extern void DefineDomain(CreateDomainStmt *stmt); -extern void RemoveDomain(List *names, DropBehavior behavior); -extern Oid DefineCompositeType(const RangeVar *typevar, List *coldeflist); - /* commands/opclasscmds.c */ extern void DefineOpClass(CreateOpClassStmt *stmt); extern void RemoveOpClass(RemoveOpClassStmt *stmt); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index cb16a30044..dc1290775d 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: nodes.h,v 1.126 2002/12/05 15:50:38 tgl Exp $ + * $Id: nodes.h,v 1.127 2002/12/06 03:28:33 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -157,6 +157,7 @@ typedef enum NodeTag T_UpdateStmt, T_SelectStmt, T_AlterTableStmt, + T_AlterDomainStmt, T_SetOperationStmt, T_GrantStmt, T_ClosePortalStmt, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 7ca79b3520..adcb6d9a55 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: parsenodes.h,v 1.218 2002/11/25 03:36:50 tgl Exp $ + * $Id: parsenodes.h,v 1.219 2002/12/06 03:28:33 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -828,6 +828,33 @@ typedef struct AlterTableStmt } AlterTableStmt; /* ---------------------- + * Alter Domain + * + * The fields are used in different ways by the different variants of + * this command. Subtypes should match AlterTable subtypes + * ---------------------- + */ +typedef struct AlterDomainStmt +{ + NodeTag type; + char subtype; /*------------ + * T = alter column default + * N = alter column drop not null + * O = alter column set not null + * C = add constraint + * X = drop constraint + * U = change owner + *------------ + */ + List *typename; /* table to work on */ + char *name; /* column or constraint name to act on, or + * new owner */ + Node *def; /* definition of default or constraint */ + DropBehavior behavior; /* RESTRICT or CASCADE for DROP cases */ +} AlterDomainStmt; + + +/* ---------------------- * Grant|Revoke Statement * ---------------------- */ diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out index 92c9cc2cc0..f91191bddb 100644 --- a/src/test/regress/expected/domain.out +++ b/src/test/regress/expected/domain.out @@ -115,7 +115,7 @@ INSERT INTO nulltest DEFAULT VALUES; ERROR: Domain dnotnull does not allow NULL values INSERT INTO nulltest values ('a', 'b', 'c', 'd', 'c'); -- Good insert into nulltest values ('a', 'b', 'c', 'd', NULL); -ERROR: Domain $1 constraint dcheck failed +ERROR: ExecEvalConstraintTest: Domain dcheck constraint $1 failed insert into nulltest values ('a', 'b', 'c', 'd', 'a'); ERROR: ExecInsert: rejected due to CHECK constraint "nulltest_col5" on "nulltest" INSERT INTO nulltest values (NULL, 'b', 'c', 'd', 'd'); @@ -127,7 +127,7 @@ ERROR: ExecInsert: Fail to add null value in not null attribute col3 INSERT INTO nulltest values ('a', 'b', 'c', NULL, 'd'); -- Good -- Test copy COPY nulltest FROM stdin; --fail -ERROR: copy: line 1, Domain $1 constraint dcheck failed +ERROR: copy: line 1, ExecEvalConstraintTest: Domain dcheck constraint $1 failed lost synchronization with server, resetting connection SET autocommit TO 'on'; -- Last row is bad @@ -158,6 +158,7 @@ ERROR: Domain dnotnull does not allow NULL values drop table nulltest; drop domain dnotnull restrict; drop domain dnull restrict; +drop domain dcheck restrict; create domain ddef1 int4 DEFAULT 3; create domain ddef2 oid DEFAULT '12'; -- Type mixing, function returns int8 @@ -191,7 +192,80 @@ select * from defaulttest; (4 rows) drop sequence ddef4_seq; -drop table defaulttest; +drop table defaulttest cascade; +-- Test ALTER DOMAIN .. NOT NULL +create domain dnotnulltest integer; +create table domnotnull +( col1 dnotnulltest +, col2 dnotnulltest +); +insert into domnotnull default values; +alter domain dnotnulltest set not null; -- fails +ERROR: ALTER DOMAIN: Relation "domnotnull" Attribute "col1" contains NULL values +update domnotnull set col1 = 5; +alter domain dnotnulltest set not null; -- fails +ERROR: ALTER DOMAIN: Relation "domnotnull" Attribute "col2" contains NULL values +update domnotnull set col2 = 6; +alter domain dnotnulltest set not null; +alter domain dnotnulltest set not null; -- fails +ERROR: AlterDomain: dnotnulltest is already set to NOT NULL +update domnotnull set col1 = null; -- fails +ERROR: Domain dnotnulltest does not allow NULL values +alter domain dnotnulltest drop not null; +alter domain dnotnulltest drop not null; -- fails +ERROR: AlterDomain: dnotnulltest is already set to NULL +update domnotnull set col1 = null; +drop domain dnotnulltest cascade; +NOTICE: Drop cascades to table domnotnull column col2 +NOTICE: Drop cascades to table domnotnull column col1 +-- Test ALTER DOMAIN .. DEFAULT .. +create table domdeftest (col1 ddef1); +insert into domdeftest default values; +select * from domdeftest; + col1 +------ + 3 +(1 row) + +alter domain ddef1 set default '42'; +insert into domdeftest default values; +select * from domdeftest; + col1 +------ + 3 + 42 +(2 rows) + +alter domain ddef1 drop default; +insert into domdeftest default values; +select * from domdeftest; + col1 +------ + 3 + 42 + +(3 rows) + +drop table domdeftest; +-- Test ALTER DOMAIN .. CONSTRAINT .. +create domain con as integer; +create table domcontest (col1 con); +insert into domcontest values (1); +insert into domcontest values (2); +alter domain con add constraint t check (VALUE < 1); -- fails +ERROR: AlterDomainAddConstraint: Domain con constraint t failed +alter domain con add constraint t check (VALUE < 34); +alter domain con add check (VALUE > 0); +insert into domcontest values (-5); -- fails +ERROR: ExecEvalConstraintTest: Domain con constraint $1 failed +insert into domcontest values (42); -- fails +ERROR: ExecEvalConstraintTest: Domain con constraint t failed +insert into domcontest values (5); +alter domain con drop constraint t; +insert into domcontest values (-5); --fails +ERROR: ExecEvalConstraintTest: Domain con constraint $1 failed +insert into domcontest values (42); +-- cleanup drop domain ddef1 restrict; drop domain ddef2 restrict; drop domain ddef3 restrict; diff --git a/src/test/regress/resultmap b/src/test/regress/resultmap index 0ab2f0e2f7..d4fe5af993 100644 --- a/src/test/regress/resultmap +++ b/src/test/regress/resultmap @@ -14,8 +14,8 @@ float8/.*-qnx=float8-exp-three-digits float8/alpha.*-dec-osf.*:cc=float8-fp-exception float8/i.86-pc-cygwin=float8-small-is-zero geometry/.*-darwin=geometry-positive-zeros -geometry/i.86-.*-freebsd4.[0-5]=geometry-positive-zeros -geometry/alpha.*-freebsd4.[0-5]=geometry-positive-zeros +geometry/i.86-.*-freebsd4.[0-7]=geometry-positive-zeros +geometry/alpha.*-freebsd4.[0-7]=geometry-positive-zeros geometry/i.86-.*-openbsd=geometry-positive-zeros geometry/sparc-.*-openbsd=geometry-positive-zeros geometry/.*-netbsd1.[0-5]=geometry-positive-zeros diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql index 65fba7466f..76060e99c8 100644 --- a/src/test/regress/sql/domain.sql +++ b/src/test/regress/sql/domain.sql @@ -127,6 +127,7 @@ SELECT cast(col4 as dnotnull) from nulltest; -- fail drop table nulltest; drop domain dnotnull restrict; drop domain dnull restrict; +drop domain dcheck restrict; create domain ddef1 int4 DEFAULT 3; @@ -159,7 +160,71 @@ COPY defaulttest(col5) FROM stdin; select * from defaulttest; drop sequence ddef4_seq; -drop table defaulttest; +drop table defaulttest cascade; + +-- Test ALTER DOMAIN .. NOT NULL +create domain dnotnulltest integer; +create table domnotnull +( col1 dnotnulltest +, col2 dnotnulltest +); + +insert into domnotnull default values; +alter domain dnotnulltest set not null; -- fails + +update domnotnull set col1 = 5; +alter domain dnotnulltest set not null; -- fails + +update domnotnull set col2 = 6; + +alter domain dnotnulltest set not null; +alter domain dnotnulltest set not null; -- fails + +update domnotnull set col1 = null; -- fails + +alter domain dnotnulltest drop not null; +alter domain dnotnulltest drop not null; -- fails + +update domnotnull set col1 = null; + +drop domain dnotnulltest cascade; + +-- Test ALTER DOMAIN .. DEFAULT .. +create table domdeftest (col1 ddef1); + +insert into domdeftest default values; +select * from domdeftest; + +alter domain ddef1 set default '42'; +insert into domdeftest default values; +select * from domdeftest; + +alter domain ddef1 drop default; +insert into domdeftest default values; +select * from domdeftest; + +drop table domdeftest; + +-- Test ALTER DOMAIN .. CONSTRAINT .. +create domain con as integer; +create table domcontest (col1 con); + +insert into domcontest values (1); +insert into domcontest values (2); +alter domain con add constraint t check (VALUE < 1); -- fails + +alter domain con add constraint t check (VALUE < 34); +alter domain con add check (VALUE > 0); + +insert into domcontest values (-5); -- fails +insert into domcontest values (42); -- fails +insert into domcontest values (5); + +alter domain con drop constraint t; +insert into domcontest values (-5); --fails +insert into domcontest values (42); + +-- cleanup drop domain ddef1 restrict; drop domain ddef2 restrict; drop domain ddef3 restrict; -- 2.11.0