diff options
Diffstat (limited to 'src/backend/commands/typecmds.c')
-rw-r--r-- | src/backend/commands/typecmds.c | 966 |
1 files changed, 819 insertions, 147 deletions
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index ab0608a08a9..ebae0e272de 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; + } +} |