📄 typecmds.c
字号:
/* * 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 = ConstraintRelationId; 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. */voidAlterDomainAddConstraint(List *names, Node *newConstraint){ TypeName *typename; Oid domainoid; Relation typrel; HeapTuple tup; Form_pg_type typTup; List *rels; ListCell *rt; EState *estate; ExprContext *econtext; char *ccbin; Expr *expr; ExprState *exprstate; 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 */ typrel = heap_open(TypeRelationId, RowExclusiveLock); /* Use LookupTypeName here so that shell types can be found (why?). */ domainoid = LookupTypeName(typename); if (!OidIsValid(domainoid)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("type \"%s\" does not exist", TypeNameToString(typename)))); tup = SearchSysCacheCopy(TYPEOID, ObjectIdGetDatum(domainoid), 0, 0, 0); if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for type %u", domainoid); typTup = (Form_pg_type) GETSTRUCT(tup); /* Doesn't return if user isn't allowed to alter the domain */ domainOwnerCheck(tup, typename); /* Check for unsupported constraint types */ if (IsA(newConstraint, FkConstraint)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("foreign key constraints not possible for domains"))); /* otherwise it should be a plain Constraint */ if (!IsA(newConstraint, Constraint)) elog(ERROR, "unrecognized node type: %d", (int) nodeTag(newConstraint)); constr = (Constraint *) newConstraint; switch (constr->contype) { case CONSTR_CHECK: /* processed below */ break; case CONSTR_UNIQUE: ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("unique constraints not possible for domains"))); break; case CONSTR_PRIMARY: ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("primary key constraints not possible for domains"))); break; case CONSTR_ATTR_DEFERRABLE: case CONSTR_ATTR_NOT_DEFERRABLE: case CONSTR_ATTR_DEFERRED: case CONSTR_ATTR_IMMEDIATE: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("specifying constraint deferrability not supported for domains"))); break; default: elog(ERROR, "unrecognized constraint subtype: %d", (int) constr->contype); break; } /* * Since all other constraint types throw errors, this must be a check * constraint. First, process the constraint expression and add an entry * to pg_constraint. */ ccbin = domainAddConstraint(HeapTupleGetOid(tup), typTup->typnamespace, typTup->typbasetype, typTup->typtypmod, constr, NameStr(typTup->typname)); /* * Test all values stored in the attributes based on the domain the * constraint is being added to. */ expr = (Expr *) stringToNode(ccbin); /* Need an EState to run ExecEvalExpr */ estate = CreateExecutorState(); econtext = GetPerTupleExprContext(estate); /* build execution state for expr */ exprstate = ExecPrepareExpr(expr, estate); /* Fetch relation list with attributes based on this domain */ /* ShareLock is sufficient to prevent concurrent data changes */ rels = get_rels_with_domain(domainoid, ShareLock); foreach(rt, rels) { RelToCheck *rtc = (RelToCheck *) lfirst(rt); Relation testrel = rtc->rel; TupleDesc tupdesc = RelationGetDescr(testrel); HeapScanDesc scan; HeapTuple tuple; /* Scan all tuples in this relation */ scan = heap_beginscan(testrel, SnapshotNow, 0, NULL); while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) { int i; /* Test attributes that are of the domain */ for (i = 0; i < rtc->natts; i++) { int attnum = rtc->atts[i]; Datum d; bool isNull; Datum conResult; d = heap_getattr(tuple, attnum, tupdesc, &isNull); econtext->domainValue_datum = d; econtext->domainValue_isNull = isNull; conResult = ExecEvalExprSwitchContext(exprstate, econtext, &isNull, NULL); if (!isNull && !DatumGetBool(conResult)) ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION), errmsg("column \"%s\" of table \"%s\" contains values that violate the new constraint", NameStr(tupdesc->attrs[attnum - 1]->attname), RelationGetRelationName(testrel)))); } ResetExprContext(econtext); } heap_endscan(scan); /* Hold relation lock till commit (XXX bad for concurrency) */ heap_close(testrel, NoLock); } FreeExecutorState(estate); /* Clean up */ heap_close(typrel, RowExclusiveLock);}/* * get_rels_with_domain * * Fetch all relations / attributes which are using the domain * * The result is a list of RelToCheck structs, one for each distinct * relation, each containing one or more attribute numbers that are of * the domain type. We have opened each rel and acquired the specified lock * type on it. * * XXX this is completely broken because there is no way to lock the domain * to prevent columns from being added or dropped while our command runs. * We can partially protect against column drops by locking relations as we * come across them, but there is still a race condition (the window between * seeing a pg_depend entry and acquiring lock on the relation it references). * Also, holding locks on all these relations simultaneously creates a non- * trivial risk of deadlock. We can minimize but not eliminate the deadlock * risk by using the weakest suitable lock (ShareLock for most callers). * * XXX to support domains over domains, we'd need to make this smarter, * or make its callers smarter, so that we could find columns of derived * domains. Arrays of domains would be a problem too. * * Generally used for retrieving a list of tests when adding * new constraints to a domain. */static List *get_rels_with_domain(Oid domainOid, LOCKMODE lockmode){ List *result = NIL; Relation depRel; ScanKeyData key[2]; SysScanDesc depScan; HeapTuple depTup; /* * We scan pg_depend to find those things that depend on the domain. (We * assume we can ignore refobjsubid for a domain.) */ depRel = heap_open(DependRelationId, AccessShareLock); ScanKeyInit(&key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(TypeRelationId)); ScanKeyInit(&key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(domainOid)); depScan = systable_beginscan(depRel, DependReferenceIndexId, true, SnapshotNow, 2, key); while (HeapTupleIsValid(depTup = systable_getnext(depScan))) { Form_pg_depend pg_depend = (Form_pg_depend) GETSTRUCT(depTup); RelToCheck *rtc = NULL; ListCell *rellist; Form_pg_attribute pg_att; int ptr; /* Ignore dependees that aren't user columns of relations */ /* (we assume system columns are never of domain types) */ if (pg_depend->classid != RelationRelationId || pg_depend->objsubid <= 0) continue; /* See if we already have an entry for this relation */ foreach(rellist, result) { RelToCheck *rt = (RelToCheck *) lfirst(rellist); if (RelationGetRelid(rt->rel) == pg_depend->objid) { rtc = rt; break; } } if (rtc == NULL) { /* First attribute found for this relation */ Relation rel; /* Acquire requested lock on relation */ rel = relation_open(pg_depend->objid, lockmode); /* It could be a view or composite type; if so ignore it */ if (rel->rd_rel->relkind != RELKIND_RELATION) { relation_close(rel, lockmode); continue; } /* Build the RelToCheck entry with enough space for all atts */ rtc = (RelToCheck *) palloc(sizeof(RelToCheck)); rtc->rel = rel; rtc->natts = 0; rtc->atts = (int *) palloc(sizeof(int) * RelationGetNumberOfAttributes(rel)); result = lcons(rtc, result); } /* * Confirm column has not been dropped, and is of the expected type. * This defends against an ALTER DROP COLUMN occuring just before we * acquired lock ... but if the whole table were dropped, we'd still * have a problem. */ if (pg_depend->objsubid > RelationGetNumberOfAttributes(rtc->rel)) continue; pg_att = rtc->rel->rd_att->attrs[pg_depend->objsubid - 1]; if (pg_att->attisdropped || pg_att->atttypid != domainOid) continue; /* * Okay, add column to result. We store the columns in column-number * order; this is just a hack to improve predictability of regression * test output ... */ Assert(rtc->natts < RelationGetNumberOfAttributes(rtc->rel)); ptr = rtc->natts++; while (ptr > 0 && rtc->atts[ptr - 1] > pg_depend->objsubid) { rtc->atts[ptr] = rtc->atts[ptr - 1]; ptr--; } rtc->atts[ptr] = pg_depend->objsubid; } systable_endscan(depScan); relation_close(depRel, AccessShareLock); return result;}/* * domainOwnerCheck * * 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. */static voiddomainOwnerCheck(HeapTuple tup, TypeName *typename){ Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup); /* Check that this is actually a domain */ if (typTup->typtype != 'd') ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a domain", TypeNameToString(typename)))); /* Permission check: must own type */ if (!pg_type_ownercheck(HeapTupleGetOid(tup), GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE, TypeNameToString(typename));}/* * domainAddConstraint - code shared between CREATE and ALTER DOMAIN */static char *domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid, int typMod, Constraint *constr, char *domainName){ Node *expr; char *ccsrc; char *ccbin; ParseState *pstate; CoerceToDomainValue *domVal; /* * Assign or validate constraint name */ if (constr->name) { if (ConstraintNameIsUsed(CONSTRAINT_DOMAIN, domainOid, domainNamespace, constr->name)) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("constraint \"%s\" for domain \"%s\" already exists", constr->name, domainName))); } else constr->name = ChooseConstraintName(domainName, NULL, "check", domainNamespace, NIL); /* * Convert the A_EXPR in raw_expr into an EXPR */ pstate = make_parsestate(NULL); /* * Set up a CoerceToDomainValue to represent the occurrence of VALUE in * the expression. Note that it will appear to have the type of the base * type, not the domain. This seems correct since within the check * expression, we should not assume the input value can be considered a * member of the domain. */ domVal = makeNode(CoerceToDomainValue); domVal->typeId = baseTypeOid; domVal->typeMod = typMod; pstate->p_value_substitute = (Node *) domVal; expr = transformExpr(pstate, constr->raw_expr); /* * Make sure it yields a boolean result. */ expr = coerce_to_boolean(pstate, expr, "CHECK"); /* * Make sure no outside relations are referred to. */ if (list_length(pstate->p_rtable) != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("cannot use table references in domain check constraint"))); /* * Domains don't allow var clauses (this should be redundant with the * above check, but make it anyway) */ if (contain_var_clause(expr)) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("cannot use table references in domain check constraint"))); /* * No subplans or aggregates, either... */ if (pstate->p_hasSubLinks) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot use subquery in check constraint"))); if (pstate->p_hasAggs) ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR), errmsg("cannot use aggregate in check constraint"))); /* * Convert to string form for storage. */ ccbin = nodeToString(expr); /* * Deparse it to produce text for consrc. *
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -