From 6b603e67dcd1a93a56f3c6b5f36fd8f08e2ee35d Mon Sep 17 00:00:00 2001 From: Bruce Momjian <bruce@momjian.us> Date: Fri, 15 Nov 2002 02:50:21 +0000 Subject: [PATCH] Add DOMAIN check constraints. Rod Taylor --- doc/src/sgml/ref/create_domain.sgml | 12 +- src/backend/catalog/heap.c | 12 +- src/backend/catalog/pg_constraint.c | 74 ++++++++-- src/backend/commands/tablecmds.c | 16 ++- src/backend/commands/typecmds.c | 199 +++++++++++++++++++++++---- src/backend/executor/execQual.c | 46 ++++++- src/backend/nodes/copyfuncs.c | 32 ++++- src/backend/nodes/equalfuncs.c | 27 +++- src/backend/nodes/outfuncs.c | 30 +++- src/backend/nodes/readfuncs.c | 50 ++++++- src/backend/optimizer/util/clauses.c | 13 +- src/backend/parser/analyze.c | 4 +- src/backend/parser/gram.y | 10 +- src/backend/parser/keywords.c | 3 +- src/backend/parser/parse_clause.c | 12 +- src/backend/parser/parse_coerce.c | 56 +++++++- src/backend/parser/parse_expr.c | 57 ++++---- src/backend/parser/parse_node.c | 6 +- src/backend/parser/parse_target.c | 4 +- src/backend/utils/adt/ruleutils.c | 8 +- src/include/catalog/indexing.h | 5 +- src/include/catalog/pg_constraint.h | 19 ++- src/include/nodes/execnodes.h | 9 +- src/include/nodes/nodes.h | 4 +- src/include/nodes/parsenodes.h | 19 ++- src/include/optimizer/var.h | 3 +- src/include/parser/parse_expr.h | 5 +- src/test/regress/expected/domain.out | 32 +++-- src/test/regress/sql/domain.sql | 21 ++- 29 files changed, 641 insertions(+), 147 deletions(-) diff --git a/doc/src/sgml/ref/create_domain.sgml b/doc/src/sgml/ref/create_domain.sgml index b7d5e606d3e..bd4114688f6 100644 --- a/doc/src/sgml/ref/create_domain.sgml +++ b/doc/src/sgml/ref/create_domain.sgml @@ -1,5 +1,5 @@ <!-- -$Header: /cvsroot/pgsql/doc/src/sgml/ref/create_domain.sgml,v 1.6 2002/09/20 03:39:15 momjian Exp $ +$Header: /cvsroot/pgsql/doc/src/sgml/ref/create_domain.sgml,v 1.7 2002/11/15 02:50:05 momjian Exp $ PostgreSQL documentation --> @@ -200,16 +200,6 @@ CREATE TABLE countrylist (id INT4, country country_code); </para> </refsect1> - <refsect1 id="SQL-CREATEDOMAIN-compatibility"> - <title>Compatibility</title> - - <para> - SQL99 defines CREATE DOMAIN, but says that the only allowed constraint - type is CHECK constraints. CHECK constraints for domains are not yet - supported by <productname>PostgreSQL</productname>. - </para> - </refsect1> - <refsect1 id="SQL-CREATEDOMAIN-see-also"> <title>See Also</title> diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 9956672c12a..f8f667e24a9 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.234 2002/11/11 22:19:21 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.235 2002/11/15 02:50:05 momjian Exp $ * * * INTERFACE ROUTINES @@ -1500,7 +1500,8 @@ AddRelationRawConstraints(Relation rel, ccname = cdef->name; /* Check against pre-existing constraints */ - if (ConstraintNameIsUsed(RelationGetRelid(rel), + if (ConstraintNameIsUsed(CONSTRAINT_RELATION, + RelationGetRelid(rel), RelationGetNamespace(rel), ccname)) elog(ERROR, "constraint \"%s\" already exists for relation \"%s\"", @@ -1534,7 +1535,8 @@ AddRelationRawConstraints(Relation rel, * pre-existing constraints, nor with any auto-generated * names so far. */ - ccname = GenerateConstraintName(RelationGetRelid(rel), + ccname = GenerateConstraintName(CONSTRAINT_RELATION, + RelationGetRelid(rel), RelationGetNamespace(rel), &constr_name_ctr); @@ -1565,7 +1567,7 @@ AddRelationRawConstraints(Relation rel, /* * Transform raw parsetree to executable expression. */ - expr = transformExpr(pstate, cdef->raw_expr); + expr = transformExpr(pstate, cdef->raw_expr, NULL); /* * Make sure it yields a boolean result. @@ -1694,7 +1696,7 @@ cookDefault(ParseState *pstate, /* * Transform raw parsetree to executable expression. */ - expr = transformExpr(pstate, raw_default); + expr = transformExpr(pstate, raw_default, NULL); /* * Make sure default expr does not refer to any vars. diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index e03e545beb3..7cd105928c3 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.7 2002/09/22 00:37:09 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/pg_constraint.c,v 1.8 2002/11/15 02:50:05 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -190,6 +190,19 @@ CreateConstraintEntry(const char *constraintName, } } + if (OidIsValid(domainId)) + { + /* + * Register auto dependency from constraint to owning domain + */ + ObjectAddress domobject; + + domobject.classId = RelOid_pg_type; + domobject.objectId = domainId; + + recordDependencyOn(&conobject, &domobject, DEPENDENCY_AUTO); + } + if (OidIsValid(foreignRelId)) { /* @@ -262,7 +275,7 @@ CreateConstraintEntry(const char *constraintName, * this test is not very meaningful. */ bool -ConstraintNameIsUsed(Oid relId, Oid relNamespace, const char *cname) +ConstraintNameIsUsed(CONSTRAINTCATEGORY conCat, Oid objId, Oid objNamespace, const char *cname) { bool found; Relation conDesc; @@ -280,7 +293,7 @@ ConstraintNameIsUsed(Oid relId, Oid relNamespace, const char *cname) ScanKeyEntryInitialize(&skey[1], 0x0, Anum_pg_constraint_connamespace, F_OIDEQ, - ObjectIdGetDatum(relNamespace)); + ObjectIdGetDatum(objNamespace)); conscan = systable_beginscan(conDesc, ConstraintNameNspIndex, true, SnapshotNow, 2, skey); @@ -289,7 +302,12 @@ ConstraintNameIsUsed(Oid relId, Oid relNamespace, const char *cname) { Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tup); - if (con->conrelid == relId) + if (conCat == CONSTRAINT_RELATION && con->conrelid == objId) + { + found = true; + break; + } + else if (conCat == CONSTRAINT_DOMAIN && con->contypid == objId) { found = true; break; @@ -314,7 +332,7 @@ ConstraintNameIsUsed(Oid relId, Oid relNamespace, const char *cname) * someone else might choose the same name concurrently! */ char * -GenerateConstraintName(Oid relId, Oid relNamespace, int *counter) +GenerateConstraintName(CONSTRAINTCATEGORY conCat, Oid objId, Oid objNamespace, int *counter) { bool found; Relation conDesc; @@ -347,7 +365,7 @@ GenerateConstraintName(Oid relId, Oid relNamespace, int *counter) ScanKeyEntryInitialize(&skey[1], 0x0, Anum_pg_constraint_connamespace, F_OIDEQ, - ObjectIdGetDatum(relNamespace)); + ObjectIdGetDatum(objNamespace)); conscan = systable_beginscan(conDesc, ConstraintNameNspIndex, true, SnapshotNow, 2, skey); @@ -356,7 +374,12 @@ GenerateConstraintName(Oid relId, Oid relNamespace, int *counter) { Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tup); - if (con->conrelid == relId) + if (conCat == CONSTRAINT_RELATION && con->conrelid == objId) + { + found = true; + break; + } + else if (conCat == CONSTRAINT_DOMAIN && con->contypid == objId) { found = true; break; @@ -415,10 +438,13 @@ RemoveConstraintById(Oid conId) con = (Form_pg_constraint) GETSTRUCT(tup); /* - * If the constraint is for a relation, open and exclusive-lock the - * relation it's for. + * If the constraint is for a relation, open and exclusive-lock + * the relation it's for. + * + * If the constraint is for a domain, open and lock the pg_type entry + * tye constraint is used on. * - * XXX not clear what we should lock, if anything, for other constraints. + * XXX not clear what we should lock, if anything, for assert constraints. */ if (OidIsValid(con->conrelid)) { @@ -463,6 +489,34 @@ RemoveConstraintById(Oid conId) /* Keep lock on constraint's rel until end of xact */ heap_close(rel, NoLock); } + /* Lock the domain row in pg_type */ + else if (OidIsValid(con->contypid)) + { + Relation typRel; + HeapTuple typTup; + ScanKeyData typKey[1]; + SysScanDesc typScan; + + typRel = heap_openr(TypeRelationName, RowExclusiveLock); + + ScanKeyEntryInitialize(&typKey[0], 0x0, + Anum_pg_constraint_contypid, F_OIDEQ, + ObjectIdGetDatum(con->contypid)); + + typScan = systable_beginscan(typRel, TypeOidIndex, true, + SnapshotNow, 1, typKey); + + typTup = systable_getnext(typScan); + + if (!HeapTupleIsValid(typTup)) + elog(ERROR, "RemoveConstraintById: Type %d does not exist", + con->contypid); + + systable_endscan(typScan); + + /* Keep lock on domain type until end of xact */ + heap_close(typRel, NoLock); + } /* Fry the constraint itself */ simple_heap_delete(conDesc, &tup->t_self); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 8023ba83420..cda8687e448 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.53 2002/11/11 22:19:21 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.54 2002/11/15 02:50:05 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -2632,14 +2632,16 @@ AlterTableAddConstraint(Oid myrelid, bool recurse, */ if (constr->name) { - if (ConstraintNameIsUsed(RelationGetRelid(rel), + if (ConstraintNameIsUsed(CONSTRAINT_RELATION, + RelationGetRelid(rel), RelationGetNamespace(rel), constr->name)) elog(ERROR, "constraint \"%s\" already exists for relation \"%s\"", constr->name, RelationGetRelationName(rel)); } else - constr->name = GenerateConstraintName(RelationGetRelid(rel), + constr->name = GenerateConstraintName(CONSTRAINT_RELATION, + RelationGetRelid(rel), RelationGetNamespace(rel), &counter); @@ -2668,7 +2670,8 @@ AlterTableAddConstraint(Oid myrelid, bool recurse, */ if (fkconstraint->constr_name) { - if (ConstraintNameIsUsed(RelationGetRelid(rel), + if (ConstraintNameIsUsed(CONSTRAINT_RELATION, + RelationGetRelid(rel), RelationGetNamespace(rel), fkconstraint->constr_name)) elog(ERROR, "constraint \"%s\" already exists for relation \"%s\"", @@ -2676,7 +2679,8 @@ AlterTableAddConstraint(Oid myrelid, bool recurse, RelationGetRelationName(rel)); } else - fkconstraint->constr_name = GenerateConstraintName(RelationGetRelid(rel), + fkconstraint->constr_name = GenerateConstraintName(CONSTRAINT_RELATION, + RelationGetRelid(rel), RelationGetNamespace(rel), &counter); @@ -2734,7 +2738,7 @@ AlterTableAddCheckConstraint(Relation rel, Constraint *constr) /* * Convert the A_EXPR in raw_expr into an EXPR */ - expr = transformExpr(pstate, constr->raw_expr); + expr = transformExpr(pstate, constr->raw_expr, NULL); /* * Make sure it yields a boolean result. diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index a573cac27de..ab0608a08a9 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.16 2002/11/11 22:19:22 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/typecmds.c,v 1.17 2002/11/15 02:50:06 momjian Exp $ * * DESCRIPTION * The "DefineFoo" routines take the parse tree and pick out the @@ -36,10 +36,17 @@ #include "catalog/dependency.h" #include "catalog/heap.h" #include "catalog/namespace.h" +#include "catalog/pg_constraint.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "commands/tablecmds.h" #include "miscadmin.h" +#include "nodes/nodes.h" +#include "optimizer/clauses.h" +#include "optimizer/planmain.h" +#include "optimizer/var.h" +#include "parser/parse_coerce.h" +#include "parser/parse_expr.h" #include "parser/parse_func.h" #include "parser/parse_type.h" #include "utils/acl.h" @@ -406,7 +413,8 @@ DefineDomain(CreateDomainStmt *stmt) List *listptr; Oid basetypeoid; Oid domainoid; - Form_pg_type baseType; + Form_pg_type baseType; + int counter = 0; /* Convert list of names to a name and namespace */ domainNamespace = QualifiedNameGetCreationNamespace(stmt->domainname, @@ -484,17 +492,21 @@ DefineDomain(CreateDomainStmt *stmt) basetypelem = baseType->typelem; /* - * Run through constraints manually to avoid the additional processing - * conducted by DefineRelation() and friends. - * - * Besides, we don't want any constraints to be cooked. We'll do that - * when the table is created via MergeDomainAttributes(). + * Run through constraints manually to avoid the additional + * processing conducted by DefineRelation() and friends. */ foreach(listptr, schema) { - Constraint *colDef = lfirst(listptr); + Node *newConstraint = lfirst(listptr); + 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"); + + colDef = (Constraint *) newConstraint; + switch (colDef->contype) { /* @@ -546,26 +558,26 @@ DefineDomain(CreateDomainStmt *stmt) elog(ERROR, "CREATE DOMAIN has conflicting NULL / NOT NULL constraint"); typNotNull = false; nullDefined = true; - break; + break; - case CONSTR_UNIQUE: - elog(ERROR, "CREATE DOMAIN / UNIQUE indexes not supported"); - 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; + case CONSTR_PRIMARY: + elog(ERROR, "CREATE DOMAIN / PRIMARY KEY indexes not supported"); + break; - case CONSTR_CHECK: - elog(ERROR, "DefineDomain: CHECK Constraints not supported"); - break; + /* Check constraints are handled after domain creation */ + 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; + 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: elog(ERROR, "DefineDomain: unrecognized constraint node type"); @@ -591,12 +603,139 @@ DefineDomain(CreateDomainStmt *stmt) basetypeoid, /* base type ID */ defaultValue, /* default type value (text) */ defaultValueBin, /* default type value (binary) */ - byValue, /* passed by value */ - alignment, /* required alignment */ - storage, /* TOAST strategy */ - stmt->typename->typmod, /* typeMod value */ - typNDims, /* Array dimensions for base type */ - typNotNull); /* Type NOT NULL */ + byValue, /* passed by value */ + alignment, /* required alignment */ + storage, /* TOAST strategy */ + stmt->typename->typmod, /* typeMod value */ + typNDims, /* Array dimensions for base type */ + typNotNull); /* Type NOT NULL */ + + /* + * Process constraints which refer to the domain ID returned by TypeCreate + */ + 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 */ + } + break; + default: + break; + } + } /* * Add any dependencies needed for the default expression. diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 5718983ca10..1612a2d9ea2 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.108 2002/09/04 20:31:17 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.109 2002/11/15 02:50:06 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -72,6 +72,9 @@ static Datum ExecEvalBooleanTest(BooleanTest *btest, ExprContext *econtext, static Datum ExecEvalConstraintTest(ConstraintTest *constraint, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalConstraintTestValue(ConstraintTestValue *conVal, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); /*---------- @@ -1551,6 +1554,23 @@ ExecEvalBooleanTest(BooleanTest *btest, } } +/* + * ExecEvalConstraintTestValue + * + * Return the value stored by constraintTest. + */ +static Datum +ExecEvalConstraintTestValue(ConstraintTestValue *conVal, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + /* + * If the Datum hasn't been set, then it's ExecEvalConstraintTest + * hasn't been called. + */ + *isNull = econtext->domainValue_isNull; + return econtext->domainValue_datum; +} + /* * ExecEvalConstraintTest * @@ -1571,11 +1591,22 @@ ExecEvalConstraintTest(ConstraintTest *constraint, ExprContext *econtext, case CONSTR_TEST_NOTNULL: if (*isNull) elog(ERROR, "Domain %s does not allow NULL values", - constraint->name); + constraint->domname); break; case CONSTR_TEST_CHECK: - /* TODO: Add CHECK Constraints to domains */ - elog(ERROR, "Domain CHECK Constraints not yet implemented"); + { + Datum conResult; + + /* Var with attnum == UnassignedAttrNum uses the result */ + econtext->domainValue_datum = result; + econtext->domainValue_isNull = *isNull; + + conResult = ExecEvalExpr(constraint->check_expr, econtext, isNull, isDone); + + if (!DatumGetBool(conResult)) + elog(ERROR, "Domain %s constraint %s failed", + constraint->name, constraint->domname); + } break; default: elog(ERROR, "ExecEvalConstraintTest: Constraint type unknown"); @@ -1777,7 +1808,12 @@ ExecEvalExpr(Node *expression, isNull, isDone); break; - + case T_ConstraintTestValue: + retDatum = ExecEvalConstraintTestValue((ConstraintTestValue *) expression, + econtext, + isNull, + isDone); + break; default: elog(ERROR, "ExecEvalExpr: unknown expression type %d", nodeTag(expression)); diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index c354abf5dad..9dc29584e82 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.217 2002/11/11 22:19:22 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.218 2002/11/15 02:50:06 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -1056,11 +1056,35 @@ _copyConstraintTest(ConstraintTest *from) newnode->testtype = from->testtype; if (from->name) newnode->name = pstrdup(from->name); + if (from->domname) + newnode->domname = pstrdup(from->domname); Node_Copy(from, newnode, check_expr); return newnode; } +static ConstraintTestValue * +_copyConstraintTestValue(ConstraintTestValue *from) +{ + ConstraintTestValue *newnode = makeNode(ConstraintTestValue); + + /* + * copy remainder of node + */ + newnode->typeId = from->typeId; + newnode->typeMod = from->typeMod; + + return newnode; +} + +static DomainConstraintValue * +_copyDomainConstraintValue(DomainConstraintValue *from) +{ + DomainConstraintValue *newnode = makeNode(DomainConstraintValue); + + return newnode; +} + static ArrayRef * _copyArrayRef(ArrayRef *from) { @@ -3252,6 +3276,9 @@ copyObject(void *from) case T_ConstraintTest: retval = _copyConstraintTest(from); break; + case T_ConstraintTestValue: + retval = _copyConstraintTestValue(from); + break; case T_FkConstraint: retval = _copyFkConstraint(from); break; @@ -3264,6 +3291,9 @@ copyObject(void *from) case T_InsertDefault: retval = _copyInsertDefault(from); break; + case T_DomainConstraintValue: + retval = _copyDomainConstraintValue(from); + break; default: elog(ERROR, "copyObject: don't know how to copy node type %d", diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index ab84f5d3d20..68e93e48b08 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -20,7 +20,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.163 2002/11/11 22:19:22 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.164 2002/11/15 02:50:06 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -1971,15 +1971,32 @@ _equalConstraintTest(ConstraintTest *a, ConstraintTest *b) return false; if (!equalstr(a->name, b->name)) return false; + if (!equalstr(a->domname, b->domname)) + return false; if (!equal(a->check_expr, b->check_expr)) return false; return true; } +static bool +_equalConstraintTestValue(ConstraintTestValue *a, ConstraintTestValue *b) +{ + if (a->typeId != b->typeId) + return false; + if (a->typeMod != b->typeMod) + return false; + return true; +} + +static bool +_equalDomainConstraintValue(DomainConstraintValue *a, DomainConstraintValue *b) +{ + return true; +} + /* * Stuff from pg_list.h */ - static bool _equalValue(Value *a, Value *b) { @@ -2438,6 +2455,9 @@ equal(void *a, void *b) case T_ConstraintTest: retval = _equalConstraintTest(a, b); break; + case T_ConstraintTestValue: + retval = _equalConstraintTestValue(a, b); + break; case T_FkConstraint: retval = _equalFkConstraint(a, b); break; @@ -2450,6 +2470,9 @@ equal(void *a, void *b) case T_InsertDefault: retval = _equalInsertDefault(a, b); break; + case T_DomainConstraintValue: + retval = _equalDomainConstraintValue(a, b); + break; default: elog(WARNING, "equal: don't know whether nodes of type %d are equal", diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index c5b5a493583..2d1f2236c9b 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -5,7 +5,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.179 2002/11/11 22:19:22 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.180 2002/11/15 02:50:07 momjian Exp $ * * NOTES * Every (plan) node in POSTGRES has an associated "out" routine which @@ -1525,10 +1525,32 @@ _outConstraintTest(StringInfo str, ConstraintTest *node) appendStringInfo(str, " :testtype %d :name ", (int) node->testtype); _outToken(str, node->name); + appendStringInfo(str, " :domain "); + _outToken(str, node->domname); appendStringInfo(str, " :check_expr "); _outNode(str, node->check_expr); } +/* + * ConstraintTestValue + */ +static void +_outConstraintTestValue(StringInfo str, ConstraintTestValue *node) +{ + appendStringInfo(str, " CONSTRAINTTESTVALUE :typeid %u :typemod %d ", + node->typeId, + node->typeMod); +} + +/* + * DomainConstraintValue + */ +static void +_outDomainConstraintValue(StringInfo str, DomainConstraintValue *node) +{ + appendStringInfo(str, " DOMAINCONSTRAINTVALUE "); +} + /* * _outNode - * converts a Node into ascii string and append it to 'str' @@ -1796,9 +1818,15 @@ _outNode(StringInfo str, void *obj) case T_ConstraintTest: _outConstraintTest(str, obj); break; + case T_ConstraintTestValue: + _outConstraintTestValue(str, obj); + break; case T_FuncCall: _outFuncCall(str, obj); break; + case T_DomainConstraintValue: + _outDomainConstraintValue(str, obj); + break; default: elog(WARNING, "_outNode: don't know how to print type %d ", diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 568bf8ee1e4..9b2198ec5a5 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.136 2002/11/06 00:00:44 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.137 2002/11/15 02:50:07 momjian Exp $ * * NOTES * Most of the read functions for plan nodes are tested. (In fact, they @@ -949,12 +949,56 @@ _readConstraintTest(void) token = pg_strtok(&length); /* now read it */ local_node->name = nullable_string(token, length); + token = pg_strtok(&length); /* get :domname */ + token = pg_strtok(&length); /* get domname */ + local_node->domname = nullable_string(token, length); + token = pg_strtok(&length); /* eat :check_expr */ local_node->check_expr = nodeRead(true); /* now read it */ return local_node; } +/* ---------------- + * _readConstraintTestValue + * + * ConstraintTestValue is a subclass of Node + * ---------------- + */ +static ConstraintTestValue * +_readConstraintTestValue(void) +{ + ConstraintTestValue *local_node; + char *token; + int length; + + local_node = makeNode(ConstraintTestValue); + token = pg_strtok(&length); /* eat :typeid */ + token = pg_strtok(&length); /* get typeid */ + local_node->typeId = atooid(token); + token = pg_strtok(&length); /* eat :typemod */ + token = pg_strtok(&length); /* get typemod */ + local_node->typeMod = atoi(token); + + return local_node; +} + +/* ---------------- + * _readDomainConstraintValue + * + * DomainConstraintValue is a subclass of Node + * ---------------- + */ +static DomainConstraintValue * +_readDomainConstraintValue(void) +{ + DomainConstraintValue *local_node; + + local_node = makeNode(DomainConstraintValue); + + return local_node; +} + /* ---------------- * _readVar * @@ -2300,6 +2344,10 @@ parsePlanString(void) return_value = _readBooleanTest(); else if (length == 14 && strncmp(token, "CONSTRAINTTEST", length) == 0) return_value = _readConstraintTest(); + else if (length == 21 && strncmp(token, "DOMAINCONSTRAINTVALUE", length) == 0) + return_value = _readDomainConstraintValue(); + else if (length == 19 && strncmp(token, "CONSTRAINTTESTVALUE", length) == 0) + return_value = _readConstraintTestValue(); else elog(ERROR, "badly formatted planstring \"%.10s\"...", token); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index f55c988bfc5..e7b4e3b5dbc 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.110 2002/11/06 22:31:24 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.111 2002/11/15 02:50:07 momjian Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -1926,6 +1926,8 @@ expression_tree_walker(Node *node, if (walker(((ConstraintTest *) node)->arg, context)) return true; return walker(((ConstraintTest *) node)->check_expr, context); + case T_ConstraintTestValue: + break; case T_SubLink: { SubLink *sublink = (SubLink *) node; @@ -2310,6 +2312,15 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } break; + case T_ConstraintTestValue: + { + ConstraintTestValue *ctest = (ConstraintTestValue *) node; + ConstraintTestValue *newnode; + + FLATCOPY(newnode, ctest, ConstraintTestValue); + return (Node *) newnode; + } + break; case T_SubLink: { /* diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 30c8e8f15b9..18144f7b941 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.253 2002/10/21 22:06:19 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.254 2002/11/15 02:50:07 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -2401,7 +2401,7 @@ transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt) Oid expected_type_id, given_type_id; - expr = transformExpr(pstate, expr); + expr = transformExpr(pstate, expr, NULL); /* Cannot contain subselects or aggregates */ if (contain_subplans(expr)) diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 724424220b6..b3ca71013c9 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.377 2002/11/13 00:44:08 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.378 2002/11/15 02:50:08 momjian Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -393,7 +393,7 @@ static void doNegateFloat(Value *v); UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNTIL UPDATE USAGE USER USING - VACUUM VALID VALIDATOR VALUES VARCHAR VARYING + VACUUM VALID VALIDATOR VALUE VALUES VARCHAR VARYING VERBOSE VERSION VIEW VOLATILE WHEN WHERE WITH WITHOUT WORK WRITE @@ -6406,6 +6406,11 @@ c_expr: columnref { $$ = (Node *) $1; } n->subselect = $2; $$ = (Node *)n; } + | VALUE + { + DomainConstraintValue *n = makeNode(DomainConstraintValue); + $$ = (Node *)n; + } ; /* @@ -7315,6 +7320,7 @@ reserved_keyword: | UNIQUE | USER | USING + | VALUE | WHEN | WHERE ; diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c index b86ffa522e4..de8a6e09b1a 100644 --- a/src/backend/parser/keywords.c +++ b/src/backend/parser/keywords.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.130 2002/11/13 00:44:09 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.131 2002/11/15 02:50:08 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -314,6 +314,7 @@ static const ScanKeyword ScanKeywords[] = { {"vacuum", VACUUM}, {"valid", VALID}, {"validator", VALIDATOR}, + {"value", VALUE}, {"values", VALUES}, {"varchar", VARCHAR}, {"varying", VARYING}, diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 245c0ba422b..d9638753746 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.98 2002/09/18 21:35:22 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.99 2002/11/15 02:50:08 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -283,7 +283,7 @@ transformJoinUsingClause(ParseState *pstate, List *leftVars, List *rightVars) * transformJoinOnClause() does. Just invoke transformExpr() to fix * up the operators, and we're done. */ - result = transformExpr(pstate, result); + result = transformExpr(pstate, result, NULL); result = coerce_to_boolean(result, "JOIN/USING"); @@ -317,7 +317,7 @@ transformJoinOnClause(ParseState *pstate, JoinExpr *j, pstate->p_namespace = makeList2(j->larg, j->rarg); /* This part is just like transformWhereClause() */ - result = transformExpr(pstate, j->quals); + result = transformExpr(pstate, j->quals, NULL); result = coerce_to_boolean(result, "JOIN/ON"); @@ -478,7 +478,7 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r) save_namespace = pstate->p_namespace; pstate->p_namespace = NIL; - funcexpr = transformExpr(pstate, r->funccallnode); + funcexpr = transformExpr(pstate, r->funccallnode, NULL); pstate->p_namespace = save_namespace; @@ -961,7 +961,7 @@ transformWhereClause(ParseState *pstate, Node *clause) if (clause == NULL) return NULL; - qual = transformExpr(pstate, clause); + qual = transformExpr(pstate, clause, NULL); qual = coerce_to_boolean(qual, "WHERE"); @@ -1104,7 +1104,7 @@ findTargetlistEntry(ParseState *pstate, Node *node, List *tlist, int clause) * willing to match a resjunk target here, though the above cases must * ignore resjunk targets. */ - expr = transformExpr(pstate, node); + expr = transformExpr(pstate, node, NULL); foreach(tl, tlist) { diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 4870b24de07..a24af2de3e1 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -8,13 +8,18 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.85 2002/10/24 22:09:00 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.86 2002/11/15 02:50:09 momjian Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" +#include "access/genam.h" +#include "access/heapam.h" +#include "catalog/catname.h" +#include "catalog/indexing.h" #include "catalog/pg_cast.h" +#include "catalog/pg_constraint.h" #include "catalog/pg_proc.h" #include "nodes/makefuncs.h" #include "optimizer/clauses.h" @@ -405,8 +410,14 @@ coerce_type_constraints(Node *arg, Oid typeId, CoercionForm cformat) for (;;) { HeapTuple tup; + HeapTuple conTup; Form_pg_type typTup; + ScanKeyData key[1]; + int nkeys = 0; + SysScanDesc scan; + Relation conRel; + tup = SearchSysCache(TYPEOID, ObjectIdGetDatum(typeId), 0, 0, 0); @@ -419,7 +430,45 @@ coerce_type_constraints(Node *arg, Oid typeId, CoercionForm cformat) if (typTup->typnotnull && notNull == NULL) notNull = pstrdup(NameStr(typTup->typname)); - /* TODO: Add CHECK Constraints to domains */ + /* Add CHECK Constraints to domains */ + conRel = heap_openr(ConstraintRelationName, RowShareLock); + + ScanKeyEntryInitialize(&key[nkeys++], 0x0, + Anum_pg_constraint_contypid, F_OIDEQ, + ObjectIdGetDatum(typeId)); + + scan = systable_beginscan(conRel, ConstraintTypidIndex, true, + SnapshotNow, nkeys, key); + + while (HeapTupleIsValid(conTup = systable_getnext(scan))) + { + Datum val; + bool isNull; + ConstraintTest *r = makeNode(ConstraintTest); + Form_pg_constraint c = (Form_pg_constraint) GETSTRUCT(conTup); + + /* Not expecting conbin to be NULL, but we'll test for it anyway */ + val = fastgetattr(conTup, + Anum_pg_constraint_conbin, + conRel->rd_att, &isNull); + + if (isNull) + elog(ERROR, "coerce_type_constraints: domain %s constraint %s has NULL conbin", + NameStr(typTup->typname), NameStr(c->conname)); + + r->arg = arg; + r->testtype = CONSTR_TEST_CHECK; + r->name = NameStr(c->conname); + r->domname = NameStr(typTup->typname); + r->check_expr = stringToNode(MemoryContextStrdup(CacheMemoryContext, + DatumGetCString(DirectFunctionCall1(textout, + val)))); + + arg = (Node *) r; + } + + systable_endscan(scan); + heap_close(conRel, RowShareLock); if (typTup->typtype != 'd') { @@ -452,7 +501,8 @@ coerce_type_constraints(Node *arg, Oid typeId, CoercionForm cformat) r->arg = arg; r->testtype = CONSTR_TEST_NOTNULL; - r->name = notNull; + r->name = "NOT NULL"; + r->domname = notNull; r->check_expr = NULL; arg = (Node *) r; diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 3873fd37f0d..9a4a9c8bc91 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.129 2002/09/18 21:35:22 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.130 2002/11/15 02:50:09 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -20,6 +20,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/params.h" +#include "optimizer/clauses.h" #include "parser/analyze.h" #include "parser/gramparse.h" #include "parser/parse.h" @@ -83,7 +84,7 @@ parse_expr_init(void) * input and output of transformExpr; see SubLink for example. */ Node * -transformExpr(ParseState *pstate, Node *expr) +transformExpr(ParseState *pstate, Node *expr, ConstraintTestValue *domVal) { Node *result = NULL; @@ -152,7 +153,7 @@ transformExpr(ParseState *pstate, Node *expr) ExprFieldSelect *efs = (ExprFieldSelect *) expr; List *fields; - result = transformExpr(pstate, efs->arg); + result = transformExpr(pstate, efs->arg, domVal); /* handle qualification, if any */ foreach(fields, efs->fields) { @@ -169,7 +170,7 @@ transformExpr(ParseState *pstate, Node *expr) case T_TypeCast: { TypeCast *tc = (TypeCast *) expr; - Node *arg = transformExpr(pstate, tc->arg); + Node *arg = transformExpr(pstate, tc->arg, domVal); result = typecast_expression(arg, tc->typename); break; @@ -204,14 +205,14 @@ transformExpr(ParseState *pstate, Node *expr) n->arg = a->lexpr; result = transformExpr(pstate, - (Node *) n); + (Node *) n, domVal); } else { Node *lexpr = transformExpr(pstate, - a->lexpr); + a->lexpr, domVal); Node *rexpr = transformExpr(pstate, - a->rexpr); + a->rexpr, domVal); result = (Node *) make_op(a->name, lexpr, @@ -222,9 +223,9 @@ transformExpr(ParseState *pstate, Node *expr) case AND: { Node *lexpr = transformExpr(pstate, - a->lexpr); + a->lexpr, domVal); Node *rexpr = transformExpr(pstate, - a->rexpr); + a->rexpr, domVal); Expr *expr = makeNode(Expr); lexpr = coerce_to_boolean(lexpr, "AND"); @@ -239,9 +240,9 @@ transformExpr(ParseState *pstate, Node *expr) case OR: { Node *lexpr = transformExpr(pstate, - a->lexpr); + a->lexpr, domVal); Node *rexpr = transformExpr(pstate, - a->rexpr); + a->rexpr, domVal); Expr *expr = makeNode(Expr); lexpr = coerce_to_boolean(lexpr, "OR"); @@ -256,7 +257,7 @@ transformExpr(ParseState *pstate, Node *expr) case NOT: { Node *rexpr = transformExpr(pstate, - a->rexpr); + a->rexpr, domVal); Expr *expr = makeNode(Expr); rexpr = coerce_to_boolean(rexpr, "NOT"); @@ -270,9 +271,9 @@ transformExpr(ParseState *pstate, Node *expr) case DISTINCT: { Node *lexpr = transformExpr(pstate, - a->lexpr); + a->lexpr, domVal); Node *rexpr = transformExpr(pstate, - a->rexpr); + a->rexpr, domVal); result = (Node *) make_op(a->name, lexpr, @@ -293,7 +294,7 @@ transformExpr(ParseState *pstate, Node *expr) * Will result in a boolean constant node. */ Node *lexpr = transformExpr(pstate, - a->lexpr); + a->lexpr, domVal); ltype = exprType(lexpr); foreach(telem, (List *) a->rexpr) @@ -317,7 +318,7 @@ transformExpr(ParseState *pstate, Node *expr) n->val.val.str = (matched ? "t" : "f"); n->typename = SystemTypeName("bool"); - result = transformExpr(pstate, (Node *) n); + result = transformExpr(pstate, (Node *) n, domVal); } break; } @@ -331,7 +332,7 @@ transformExpr(ParseState *pstate, Node *expr) /* transform the list of arguments */ foreach(args, fn->args) lfirst(args) = transformExpr(pstate, - (Node *) lfirst(args)); + (Node *) lfirst(args), domVal); result = ParseFuncOrColumn(pstate, fn->funcname, fn->args, @@ -405,7 +406,7 @@ transformExpr(ParseState *pstate, Node *expr) List *elist; foreach(elist, left_list) - lfirst(elist) = transformExpr(pstate, lfirst(elist)); + lfirst(elist) = transformExpr(pstate, lfirst(elist), domVal); Assert(IsA(sublink->oper, A_Expr)); op = ((A_Expr *) sublink->oper)->name; @@ -504,7 +505,7 @@ transformExpr(ParseState *pstate, Node *expr) warg = (Node *) makeSimpleA_Expr(OP, "=", c->arg, warg); } - neww->expr = transformExpr(pstate, warg); + neww->expr = transformExpr(pstate, warg, domVal); neww->expr = coerce_to_boolean(neww->expr, "CASE/WHEN"); @@ -520,7 +521,7 @@ transformExpr(ParseState *pstate, Node *expr) n->val.type = T_Null; warg = (Node *) n; } - neww->result = transformExpr(pstate, warg); + neww->result = transformExpr(pstate, warg, domVal); newargs = lappend(newargs, neww); typeids = lappendi(typeids, exprType(neww->result)); @@ -544,7 +545,7 @@ transformExpr(ParseState *pstate, Node *expr) n->val.type = T_Null; defresult = (Node *) n; } - newc->defresult = transformExpr(pstate, defresult); + newc->defresult = transformExpr(pstate, defresult, domVal); /* * Note: default result is considered the most significant @@ -580,7 +581,7 @@ transformExpr(ParseState *pstate, Node *expr) { NullTest *n = (NullTest *) expr; - n->arg = transformExpr(pstate, n->arg); + n->arg = transformExpr(pstate, n->arg, domVal); /* the argument can be any type, so don't coerce it */ result = expr; break; @@ -617,7 +618,7 @@ transformExpr(ParseState *pstate, Node *expr) clausename = NULL; /* keep compiler quiet */ } - b->arg = transformExpr(pstate, b->arg); + b->arg = transformExpr(pstate, b->arg, domVal); b->arg = coerce_to_boolean(b->arg, clausename); @@ -625,6 +626,13 @@ transformExpr(ParseState *pstate, Node *expr) break; } + case T_DomainConstraintValue: + { + result = (Node *) copyObject(domVal); + + break; + } + /********************************************* * Quietly accept node types that may be presented when we are * called on an already-transformed tree. @@ -936,6 +944,9 @@ exprType(Node *expr) case T_ConstraintTest: type = exprType(((ConstraintTest *) expr)->arg); break; + case T_ConstraintTestValue: + type = ((ConstraintTestValue *) expr)->typeId; + break; default: elog(ERROR, "exprType: Do not know how to get type for %d node", nodeTag(expr)); diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c index f0bb1856ab3..608a67921c1 100644 --- a/src/backend/parser/parse_node.c +++ b/src/backend/parser/parse_node.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_node.c,v 1.72 2002/11/13 00:39:47 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_node.c,v 1.73 2002/11/15 02:50:09 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -277,7 +277,7 @@ transformArraySubscripts(ParseState *pstate, { if (ai->lidx) { - subexpr = transformExpr(pstate, ai->lidx); + subexpr = transformExpr(pstate, ai->lidx, NULL); /* If it's not int4 already, try to coerce */ subexpr = coerce_to_target_type(subexpr, exprType(subexpr), INT4OID, -1, @@ -299,7 +299,7 @@ transformArraySubscripts(ParseState *pstate, } lowerIndexpr = lappend(lowerIndexpr, subexpr); } - subexpr = transformExpr(pstate, ai->uidx); + subexpr = transformExpr(pstate, ai->uidx, NULL); /* If it's not int4 already, try to coerce */ subexpr = coerce_to_target_type(subexpr, exprType(subexpr), INT4OID, -1, diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index c03db4f8b4b..1a2da6da1eb 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.91 2002/09/28 20:00:19 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.92 2002/11/15 02:50:09 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -56,7 +56,7 @@ transformTargetEntry(ParseState *pstate, /* Transform the node if caller didn't do it already */ if (expr == NULL) - expr = transformExpr(pstate, node); + expr = transformExpr(pstate, node, NULL); if (IsA(expr, RangeVar)) elog(ERROR, "You can't use relation names alone in the target list, try relation.*."); diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 796eaa05069..9250faff27a 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -3,7 +3,7 @@ * back to source text * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.124 2002/09/19 23:40:56 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.125 2002/11/15 02:50:09 momjian Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -2226,6 +2226,12 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_ConstraintTestValue: + { + appendStringInfo(buf, "VALUE"); + } + break; + case T_SubLink: get_sublink_expr(node, context); break; diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index fce84e72ae9..4a76a27dec6 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.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: indexing.h,v 1.76 2002/10/18 20:33:57 tgl Exp $ + * $Id: indexing.h,v 1.77 2002/11/15 02:50:10 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -40,6 +40,7 @@ #define ConstraintNameNspIndex "pg_constraint_conname_nsp_index" #define ConstraintOidIndex "pg_constraint_oid_index" #define ConstraintRelidIndex "pg_constraint_conrelid_index" +#define ConstraintTypidIndex "pg_constraint_contypid_index" #define ConversionDefaultIndex "pg_conversion_default_index" #define ConversionNameNspIndex "pg_conversion_name_nsp_index" #define ConversionOidIndex "pg_conversion_oid_index" @@ -129,6 +130,8 @@ DECLARE_UNIQUE_INDEX(pg_class_relname_nsp_index on pg_class using btree(relname DECLARE_INDEX(pg_constraint_conname_nsp_index on pg_constraint using btree(conname name_ops, connamespace oid_ops)); /* This following index is not used for a cache and is not unique */ DECLARE_INDEX(pg_constraint_conrelid_index on pg_constraint using btree(conrelid oid_ops)); +/* This following index is not used for a cache and is not unique */ +DECLARE_INDEX(pg_constraint_contypid_index on pg_constraint using btree(contypid oid_ops)); DECLARE_UNIQUE_INDEX(pg_constraint_oid_index on pg_constraint using btree(oid oid_ops)); DECLARE_UNIQUE_INDEX(pg_conversion_default_index on pg_conversion using btree(connamespace oid_ops, conforencoding int4_ops, contoencoding int4_ops, oid oid_ops)); DECLARE_UNIQUE_INDEX(pg_conversion_name_nsp_index on pg_conversion using btree(conname name_ops, connamespace oid_ops)); diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h index ffe6a740ca8..80ff185579e 100644 --- a/src/include/catalog/pg_constraint.h +++ b/src/include/catalog/pg_constraint.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_constraint.h,v 1.4 2002/09/22 00:37:09 tgl Exp $ + * $Id: pg_constraint.h,v 1.5 2002/11/15 02:50:10 momjian Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -140,6 +140,15 @@ typedef FormData_pg_constraint *Form_pg_constraint; * the FKCONSTR_MATCH_xxx constants defined in parsenodes.h. */ +/* + * Used for constraint support functions where the + * and conrelid, contypid columns being looked up + */ +typedef enum CONSTRAINTCATEGORY { + CONSTRAINT_RELATION, + CONSTRAINT_DOMAIN, + CONSTRAINT_ASSERTION +} CONSTRAINTCATEGORY; /* * prototypes for functions in pg_constraint.c @@ -166,10 +175,10 @@ extern Oid CreateConstraintEntry(const char *constraintName, extern void RemoveConstraintById(Oid conId); -extern bool ConstraintNameIsUsed(Oid relId, Oid relNamespace, - const char *cname); -extern char *GenerateConstraintName(Oid relId, Oid relNamespace, - int *counter); +extern bool ConstraintNameIsUsed(CONSTRAINTCATEGORY conCat, Oid objId, Oid objNamespace, + const char *cname); +extern char *GenerateConstraintName(CONSTRAINTCATEGORY conCat, Oid objId, Oid objNamespace, + int *counter); extern bool ConstraintNameIsGenerated(const char *cname); #endif /* PG_CONSTRAINT_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index f62d1cb8159..f955815926d 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.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: execnodes.h,v 1.77 2002/11/06 22:31:24 tgl Exp $ + * $Id: execnodes.h,v 1.78 2002/11/15 02:50:10 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -113,6 +113,13 @@ typedef struct ExprContext Datum *ecxt_aggvalues; /* precomputed values for Aggref nodes */ bool *ecxt_aggnulls; /* null flags for Aggref nodes */ + /* + * Carry the domain value through the executor for application + * in a domain constraint + */ + Datum domainValue_datum; + bool domainValue_isNull; + /* Functions to call back when ExprContext is shut down */ ExprContext_CB *ecxt_callbacks; } ExprContext; diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index cec75309210..112eac34680 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.122 2002/11/10 02:17:25 momjian Exp $ + * $Id: nodes.h,v 1.123 2002/11/15 02:50:10 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -171,6 +171,7 @@ typedef enum NodeTag T_ViewStmt, T_LoadStmt, T_CreateDomainStmt, + T_DomainConstraintValue, T_CreatedbStmt, T_DropdbStmt, T_VacuumStmt, @@ -231,6 +232,7 @@ typedef enum NodeTag T_NullTest, T_BooleanTest, T_ConstraintTest, + T_ConstraintTestValue, T_CaseExpr, T_CaseWhen, T_FkConstraint, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 962452992e8..1198a81de5e 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.213 2002/11/13 00:44:09 momjian Exp $ + * $Id: parsenodes.h,v 1.214 2002/11/15 02:50:12 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -285,9 +285,26 @@ typedef struct ConstraintTest Node *arg; /* input expression */ ConstraintTestType testtype; /* test type */ char *name; /* name of constraint (for error msgs) */ + char *domname; /* name of domain (for error messages) */ Node *check_expr; /* for CHECK test, a boolean expression */ } ConstraintTest; +/* + * Placeholder node for the value to be processed by a domains + * check constraint. + */ +typedef struct DomainConstraintValue +{ + NodeTag type; +} DomainConstraintValue; + +typedef struct ConstraintTestValue +{ + NodeTag type; + Oid typeId; + int32 typeMod; +} ConstraintTestValue; + /* * ColumnDef - column definition (used in various creates) * diff --git a/src/include/optimizer/var.h b/src/include/optimizer/var.h index a153c4d0570..68ffc8e373a 100644 --- a/src/include/optimizer/var.h +++ b/src/include/optimizer/var.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: var.h,v 1.21 2002/06/20 20:29:51 momjian Exp $ + * $Id: var.h,v 1.22 2002/11/15 02:50:21 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -22,6 +22,7 @@ extern bool contain_var_reference(Node *node, int varno, int varattno, int levelsup); extern bool contain_whole_tuple_var(Node *node, int varno, int levelsup); extern bool contain_var_clause(Node *node); +extern bool contain_var_tuple_clause(Node *node); extern List *pull_var_clause(Node *node, bool includeUpperVars); extern Node *flatten_join_alias_vars(Node *node, List *rtable, bool force); diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h index a7af335dd12..bcf84912acf 100644 --- a/src/include/parser/parse_expr.h +++ b/src/include/parser/parse_expr.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: parse_expr.h,v 1.28 2002/06/20 20:29:51 momjian Exp $ + * $Id: parse_expr.h,v 1.29 2002/11/15 02:50:21 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -21,7 +21,8 @@ extern int max_expr_depth; extern bool Transform_null_equals; -extern Node *transformExpr(ParseState *pstate, Node *expr); + +extern Node *transformExpr(ParseState *pstate, Node *expr, ConstraintTestValue *domVal); extern Oid exprType(Node *expr); extern int32 exprTypmod(Node *expr); extern bool exprIsLengthCoercion(Node *expr, int32 *coercedTypmod); diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out index e82ce6fdf08..92c9cc2cc00 100644 --- a/src/test/regress/expected/domain.out +++ b/src/test/regress/expected/domain.out @@ -103,35 +103,43 @@ drop domain domainint4arr restrict; drop domain domaintextarr restrict; create domain dnotnull varchar(15) NOT NULL; create domain dnull varchar(15); +create domain dcheck varchar(15) NOT NULL CHECK (VALUE = 'a' OR VALUE = 'c' OR VALUE = 'd'); create table nulltest ( col1 dnotnull , col2 dnotnull NULL -- NOT NULL in the domain cannot be overridden , col3 dnull NOT NULL , col4 dnull + , col5 dcheck CHECK (col5 IN ('c', 'd')) ); INSERT INTO nulltest DEFAULT VALUES; ERROR: Domain dnotnull does not allow NULL values -INSERT INTO nulltest values ('a', 'b', 'c', 'd'); -- Good -INSERT INTO nulltest values (NULL, 'b', 'c', 'd'); +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 +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'); ERROR: Domain dnotnull does not allow NULL values -INSERT INTO nulltest values ('a', NULL, 'c', 'd'); +INSERT INTO nulltest values ('a', NULL, 'c', 'd', 'c'); ERROR: Domain dnotnull does not allow NULL values -INSERT INTO nulltest values ('a', 'b', NULL, 'd'); +INSERT INTO nulltest values ('a', 'b', NULL, 'd', 'c'); ERROR: ExecInsert: Fail to add null value in not null attribute col3 -INSERT INTO nulltest values ('a', 'b', 'c', NULL); -- Good +INSERT INTO nulltest values ('a', 'b', 'c', NULL, 'd'); -- Good -- Test copy COPY nulltest FROM stdin; --fail -ERROR: copy: line 1, CopyFrom: Fail to add null value in not null attribute col3 +ERROR: copy: line 1, Domain $1 constraint dcheck failed lost synchronization with server, resetting connection SET autocommit TO 'on'; +-- Last row is bad COPY nulltest FROM stdin; +ERROR: copy: line 3, CopyFrom: rejected due to CHECK constraint "nulltest_col5" on "nulltest" +lost synchronization with server, resetting connection select * from nulltest; - col1 | col2 | col3 | col4 -------+------+------+------ - a | b | c | d - a | b | c | - a | b | c | -(3 rows) + col1 | col2 | col3 | col4 | col5 +------+------+------+------+------ + a | b | c | d | c + a | b | c | | d +(2 rows) -- Test out coerced (casted) constraints SELECT cast('1' as dnotnull); diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql index 4d210cd4aa7..65fba7466fd 100644 --- a/src/test/regress/sql/domain.sql +++ b/src/test/regress/sql/domain.sql @@ -83,29 +83,36 @@ drop domain domaintextarr restrict; create domain dnotnull varchar(15) NOT NULL; create domain dnull varchar(15); +create domain dcheck varchar(15) NOT NULL CHECK (VALUE = 'a' OR VALUE = 'c' OR VALUE = 'd'); create table nulltest ( col1 dnotnull , col2 dnotnull NULL -- NOT NULL in the domain cannot be overridden , col3 dnull NOT NULL , col4 dnull + , col5 dcheck CHECK (col5 IN ('c', 'd')) ); INSERT INTO nulltest DEFAULT VALUES; -INSERT INTO nulltest values ('a', 'b', 'c', 'd'); -- Good -INSERT INTO nulltest values (NULL, 'b', 'c', 'd'); -INSERT INTO nulltest values ('a', NULL, 'c', 'd'); -INSERT INTO nulltest values ('a', 'b', NULL, 'd'); -INSERT INTO nulltest values ('a', 'b', 'c', NULL); -- Good +INSERT INTO nulltest values ('a', 'b', 'c', 'd', 'c'); -- Good +insert into nulltest values ('a', 'b', 'c', 'd', NULL); +insert into nulltest values ('a', 'b', 'c', 'd', 'a'); +INSERT INTO nulltest values (NULL, 'b', 'c', 'd', 'd'); +INSERT INTO nulltest values ('a', NULL, 'c', 'd', 'c'); +INSERT INTO nulltest values ('a', 'b', NULL, 'd', 'c'); +INSERT INTO nulltest values ('a', 'b', 'c', NULL, 'd'); -- Good -- Test copy COPY nulltest FROM stdin; --fail -a b \N d +a b \N d \N \. SET autocommit TO 'on'; +-- Last row is bad COPY nulltest FROM stdin; -a b c \N +a b c \N c +a b c \N d +a b c \N a \. select * from nulltest; -- GitLab