From 845a6c3acccea0ec34e70808787aa7d431b0d96d Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 31 Aug 2002 22:10:48 +0000 Subject: [PATCH] Code review for domain-constraints patch. Use a new ConstraintTest node type for runtime constraint checks, instead of misusing the parse-time Constraint node for the purpose. Fix some damage introduced into type coercion logic; in particular ensure that a coerced expression tree will read out the correct result type when inspected (patch had broken some RelabelType cases). Enforce domain NOT NULL constraints against columns that are omitted from an INSERT. --- src/backend/executor/execQual.c | 91 +++++++++-------- src/backend/nodes/copyfuncs.c | 38 +++---- src/backend/nodes/equalfuncs.c | 19 +++- src/backend/nodes/outfuncs.c | 22 ++++- src/backend/nodes/readfuncs.c | 36 ++++++- src/backend/optimizer/prep/preptlist.c | 8 +- src/backend/optimizer/util/clauses.c | 33 +++---- src/backend/parser/parse_coerce.c | 131 +++++++++++++++---------- src/backend/parser/parse_expr.c | 14 +-- src/backend/parser/parse_type.c | 25 +++-- src/backend/utils/adt/ruleutils.c | 14 ++- src/backend/utils/cache/lsyscache.c | 26 +++-- src/include/nodes/nodes.h | 3 +- src/include/nodes/parsenodes.h | 28 +++++- src/include/parser/parse_coerce.h | 4 +- src/include/parser/parse_type.h | 3 +- src/include/utils/lsyscache.h | 4 +- src/test/regress/expected/domain.out | 7 +- src/test/regress/sql/domain.sql | 3 +- 19 files changed, 334 insertions(+), 175 deletions(-) diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index c53ac42b26..30f5b0e378 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.105 2002/08/31 19:10:08 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.106 2002/08/31 22:10:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -69,8 +69,9 @@ static Datum ExecEvalNullTest(NullTest *ntest, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalBooleanTest(BooleanTest *btest, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); -static Datum ExecEvalConstraint(Constraint *constraint, ExprContext *econtext, - bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalConstraintTest(ConstraintTest *constraint, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); /*---------- @@ -1465,43 +1466,6 @@ ExecEvalNullTest(NullTest *ntest, } } -/* - * ExecEvalConstraint - * - * Test the constraint against the data provided. If the data fits - * within the constraint specifications, pass it through (return the - * datum) otherwise throw an error. - */ -static Datum -ExecEvalConstraint(Constraint *constraint, ExprContext *econtext, - bool *isNull, ExprDoneCond *isDone) -{ - Datum result; - - result = ExecEvalExpr(constraint->raw_expr, econtext, isNull, isDone); - - /* Test for the constraint type */ - switch(constraint->contype) - { - case CONSTR_NOTNULL: - if (*isNull) - { - elog(ERROR, "Domain %s does not allow NULL values", constraint->name); - } - break; - case CONSTR_CHECK: - - elog(ERROR, "ExecEvalConstraint: Domain CHECK Constraints not yet implemented"); - break; - default: - elog(ERROR, "ExecEvalConstraint: Constraint type unknown"); - break; - } - - /* If all has gone well (constraint did not fail) return the datum */ - return result; -} - /* ---------------------------------------------------------------- * ExecEvalBooleanTest * @@ -1582,6 +1546,41 @@ ExecEvalBooleanTest(BooleanTest *btest, } } +/* + * ExecEvalConstraintTest + * + * Test the constraint against the data provided. If the data fits + * within the constraint specifications, pass it through (return the + * datum) otherwise throw an error. + */ +static Datum +ExecEvalConstraintTest(ConstraintTest *constraint, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + Datum result; + + result = ExecEvalExpr(constraint->arg, econtext, isNull, isDone); + + switch (constraint->testtype) + { + case CONSTR_TEST_NOTNULL: + if (*isNull) + elog(ERROR, "Domain %s does not allow NULL values", + constraint->name); + break; + case CONSTR_TEST_CHECK: + /* TODO: Add CHECK Constraints to domains */ + elog(ERROR, "Domain CHECK Constraints not yet implemented"); + break; + default: + elog(ERROR, "ExecEvalConstraintTest: Constraint type unknown"); + break; + } + + /* If all has gone well (constraint did not fail) return the datum */ + return result; +} + /* ---------------------------------------------------------------- * ExecEvalFieldSelect * @@ -1749,12 +1748,6 @@ ExecEvalExpr(Node *expression, isNull, isDone); break; - case T_Constraint: - retDatum = ExecEvalConstraint((Constraint *) expression, - econtext, - isNull, - isDone); - break; case T_CaseExpr: retDatum = ExecEvalCase((CaseExpr *) expression, econtext, @@ -1773,6 +1766,12 @@ ExecEvalExpr(Node *expression, isNull, isDone); break; + case T_ConstraintTest: + retDatum = ExecEvalConstraintTest((ConstraintTest *) expression, + econtext, + isNull, + isDone); + break; default: elog(ERROR, "ExecEvalExpr: unknown expression type %d", diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 5b35eea170..1938e7f473 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.208 2002/08/30 19:23:19 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.209 2002/08/31 22:10:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -973,10 +973,6 @@ _copyJoinExpr(JoinExpr *from) return newnode; } -/* ---------------- - * _copyCaseExpr - * ---------------- - */ static CaseExpr * _copyCaseExpr(CaseExpr *from) { @@ -994,10 +990,6 @@ _copyCaseExpr(CaseExpr *from) return newnode; } -/* ---------------- - * _copyCaseWhen - * ---------------- - */ static CaseWhen * _copyCaseWhen(CaseWhen *from) { @@ -1012,10 +1004,6 @@ _copyCaseWhen(CaseWhen *from) return newnode; } -/* ---------------- - * _copyNullTest - * ---------------- - */ static NullTest * _copyNullTest(NullTest *from) { @@ -1030,10 +1018,6 @@ _copyNullTest(NullTest *from) return newnode; } -/* ---------------- - * _copyBooleanTest - * ---------------- - */ static BooleanTest * _copyBooleanTest(BooleanTest *from) { @@ -1048,6 +1032,23 @@ _copyBooleanTest(BooleanTest *from) return newnode; } +static ConstraintTest * +_copyConstraintTest(ConstraintTest *from) +{ + ConstraintTest *newnode = makeNode(ConstraintTest); + + /* + * copy remainder of node + */ + Node_Copy(from, newnode, arg); + newnode->testtype = from->testtype; + if (from->name) + newnode->name = pstrdup(from->name); + Node_Copy(from, newnode, check_expr); + + return newnode; +} + static ArrayRef * _copyArrayRef(ArrayRef *from) { @@ -3206,6 +3207,9 @@ copyObject(void *from) case T_BooleanTest: retval = _copyBooleanTest(from); break; + case T_ConstraintTest: + retval = _copyConstraintTest(from); + break; case T_FkConstraint: retval = _copyFkConstraint(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 7c9127dbf4..10b8e79933 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.156 2002/08/30 19:23:19 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.157 2002/08/31 22:10:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1924,6 +1924,20 @@ _equalBooleanTest(BooleanTest *a, BooleanTest *b) return true; } +static bool +_equalConstraintTest(ConstraintTest *a, ConstraintTest *b) +{ + if (!equal(a->arg, b->arg)) + return false; + if (a->testtype != b->testtype) + return false; + if (!equalstr(a->name, b->name)) + return false; + if (!equal(a->check_expr, b->check_expr)) + return false; + return true; +} + /* * Stuff from pg_list.h */ @@ -2380,6 +2394,9 @@ equal(void *a, void *b) case T_BooleanTest: retval = _equalBooleanTest(a, b); break; + case T_ConstraintTest: + retval = _equalConstraintTest(a, b); + break; case T_FkConstraint: retval = _equalFkConstraint(a, b); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index a92750caef..8c25505806 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.171 2002/08/30 19:23:19 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.172 2002/08/31 22:10:43 tgl Exp $ * * NOTES * Every (plan) node in POSTGRES has an associated "out" routine which @@ -1471,7 +1471,6 @@ _outNullTest(StringInfo str, NullTest *node) { appendStringInfo(str, " NULLTEST :arg "); _outNode(str, node->arg); - appendStringInfo(str, " :nulltesttype %d ", (int) node->nulltesttype); } @@ -1484,11 +1483,25 @@ _outBooleanTest(StringInfo str, BooleanTest *node) { appendStringInfo(str, " BOOLEANTEST :arg "); _outNode(str, node->arg); - appendStringInfo(str, " :booltesttype %d ", (int) node->booltesttype); } +/* + * ConstraintTest + */ +static void +_outConstraintTest(StringInfo str, ConstraintTest *node) +{ + appendStringInfo(str, " CONSTRAINTTEST :arg "); + _outNode(str, node->arg); + appendStringInfo(str, " :testtype %d :name ", + (int) node->testtype); + _outToken(str, node->name); + appendStringInfo(str, " :check_expr "); + _outNode(str, node->check_expr); +} + /* * _outNode - * converts a Node into ascii string and append it to 'str' @@ -1750,6 +1763,9 @@ _outNode(StringInfo str, void *obj) case T_BooleanTest: _outBooleanTest(str, obj); break; + case T_ConstraintTest: + _outConstraintTest(str, obj); + break; case T_FuncCall: _outFuncCall(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 2799bb7460..4d4001d213 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.130 2002/08/30 19:23:19 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.131 2002/08/31 22:10:43 tgl Exp $ * * NOTES * Most of the read functions for plan nodes are tested. (In fact, they @@ -931,6 +931,38 @@ _readBooleanTest(void) return local_node; } +/* ---------------- + * _readConstraintTest + * + * ConstraintTest is a subclass of Node + * ---------------- + */ +static ConstraintTest * +_readConstraintTest(void) +{ + ConstraintTest *local_node; + char *token; + int length; + + local_node = makeNode(ConstraintTest); + + token = pg_strtok(&length); /* eat :arg */ + local_node->arg = nodeRead(true); /* now read it */ + + token = pg_strtok(&length); /* eat :testtype */ + token = pg_strtok(&length); /* get testtype */ + local_node->testtype = (ConstraintTestType) atoi(token); + + token = pg_strtok(&length); /* get :name */ + token = pg_strtok(&length); /* now read it */ + local_node->name = nullable_string(token, length); + + token = pg_strtok(&length); /* eat :check_expr */ + local_node->check_expr = nodeRead(true); /* now read it */ + + return local_node; +} + /* ---------------- * _readVar * @@ -2222,6 +2254,8 @@ parsePlanString(void) return_value = _readNullTest(); else if (length == 11 && strncmp(token, "BOOLEANTEST", length) == 0) return_value = _readBooleanTest(); + else if (length == 14 && strncmp(token, "CONSTRAINTTEST", length) == 0) + return_value = _readConstraintTest(); else elog(ERROR, "badly formatted planstring \"%.10s\"...", token); diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index b7c0bac12c..41f9b2f947 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.54 2002/08/02 18:15:06 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.55 2002/08/31 22:10:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -27,6 +27,7 @@ #include "nodes/makefuncs.h" #include "optimizer/prep.h" #include "parser/parsetree.h" +#include "parser/parse_coerce.h" static List *expand_targetlist(List *tlist, int command_type, @@ -162,6 +163,8 @@ expand_targetlist(List *tlist, int command_type, * * For INSERT, generate a NULL constant. (We assume the * rewriter would have inserted any available default value.) + * Also, if the column isn't dropped, apply any domain constraints + * that might exist --- this is to catch domain NOT NULL. * * For UPDATE, generate a Var reference to the existing value of * the attribute, so that it gets copied to the new tuple. @@ -182,6 +185,9 @@ expand_targetlist(List *tlist, int command_type, att_tup->attbyval, false, /* not a set */ false); + if (!att_tup->attisdropped) + new_expr = coerce_type_constraints(NULL, new_expr, + atttype, false); break; case CMD_UPDATE: /* Insert NULLs for dropped columns */ diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 9e8372139e..3b5e698839 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.106 2002/07/20 05:16:58 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.107 2002/08/31 22:10:43 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -1882,12 +1882,14 @@ expression_tree_walker(Node *node, return true; } break; - case T_Constraint: - return walker(((Constraint *) node)->raw_expr, context); case T_NullTest: return walker(((NullTest *) node)->arg, context); case T_BooleanTest: return walker(((BooleanTest *) node)->arg, context); + case T_ConstraintTest: + if (walker(((ConstraintTest *) node)->arg, context)) + return true; + return walker(((ConstraintTest *) node)->check_expr, context); case T_SubLink: { SubLink *sublink = (SubLink *) node; @@ -2238,20 +2240,6 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } break; - case T_Constraint: - { - /* - * Used for confirming domains. Only needed fields - * within the executor are the name, raw expression - * and constraint type. - */ - Constraint *con = (Constraint *) node; - Constraint *newnode; - - FLATCOPY(newnode, con, Constraint); - MUTATE(newnode->raw_expr, con->raw_expr, Node *); - return (Node *) newnode; - } case T_NullTest: { NullTest *ntest = (NullTest *) node; @@ -2272,6 +2260,17 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } break; + case T_ConstraintTest: + { + ConstraintTest *ctest = (ConstraintTest *) node; + ConstraintTest *newnode; + + FLATCOPY(newnode, ctest, ConstraintTest); + MUTATE(newnode->arg, ctest->arg, Node *); + MUTATE(newnode->check_expr, ctest->check_expr, Node *); + return (Node *) newnode; + } + break; case T_SubLink: { /* diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 1016c782d2..397bd4b8b0 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.80 2002/08/22 00:01:42 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.81 2002/08/31 22:10:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -35,7 +35,7 @@ static Node *build_func_call(Oid funcid, Oid rettype, List *args); static Oid find_coercion_function(Oid targetTypeId, Oid sourceTypeId, bool isExplicit); static Oid find_typmod_coercion_function(Oid typeId); -static Node *TypeConstraints(Node *arg, Oid typeId); + /* coerce_type() * Convert a function argument to a different type. @@ -49,7 +49,7 @@ coerce_type(ParseState *pstate, Node *node, Oid inputTypeId, if (targetTypeId == inputTypeId || node == NULL) { - /* no conversion needed, but constraints may need to be applied */ + /* no conversion needed */ result = node; } else if (inputTypeId == UNKNOWNOID && IsA(node, Const)) @@ -69,11 +69,12 @@ coerce_type(ParseState *pstate, Node *node, Oid inputTypeId, * postpone evaluation of the function call until runtime. But * there is no way to represent a typinput function call as an * expression tree, because C-string values are not Datums. + * (XXX This *is* possible as of 7.3, do we want to do it?) */ Const *con = (Const *) node; Const *newcon = makeNode(Const); Type targetType = typeidType(targetTypeId); - Oid baseTypeId = getBaseType(targetTypeId); + char targetTyptype = typeTypType(targetType); newcon->consttype = targetTypeId; newcon->constlen = typeLen(targetType); @@ -85,16 +86,31 @@ coerce_type(ParseState *pstate, Node *node, Oid inputTypeId, { char *val = DatumGetCString(DirectFunctionCall1(unknownout, con->constvalue)); + + /* + * If target is a domain, use the typmod it applies to the base + * type. Note that we call stringTypeDatum using the domain's + * pg_type row, though. This works because the domain row has + * the same typinput and typelem as the base type --- ugly... + */ + if (targetTyptype == 'd') + atttypmod = getBaseTypeMod(targetTypeId, atttypmod); + newcon->constvalue = stringTypeDatum(targetType, val, atttypmod); pfree(val); } - ReleaseSysCache(targetType); - - /* Test for domain, and apply appropriate constraints */ result = (Node *) newcon; - if (targetTypeId != baseTypeId) - result = (Node *) TypeConstraints(result, targetTypeId); + + /* + * If target is a domain, apply constraints (except for typmod, + * which we assume the input routine took care of). + */ + if (targetTyptype == 'd') + result = coerce_type_constraints(pstate, result, targetTypeId, + false); + + ReleaseSysCache(targetType); } else if (targetTypeId == ANYOID || targetTypeId == ANYARRAYOID) @@ -109,21 +125,18 @@ coerce_type(ParseState *pstate, Node *node, Oid inputTypeId, * attach a RelabelType node so that the expression will be seen * to have the intended type when inspected by higher-level code. * + * Also, domains may have value restrictions beyond the base type + * that must be accounted for. + */ + result = coerce_type_constraints(pstate, node, targetTypeId, true); + /* * XXX could we label result with exprTypmod(node) instead of * default -1 typmod, to save a possible length-coercion later? * Would work if both types have same interpretation of typmod, - * which is likely but not certain. - * - * Domains may have value restrictions beyond the base type that - * must be accounted for. + * which is likely but not certain (wrong if target is a domain, + * in any case). */ - Oid baseTypeId = getBaseType(targetTypeId); - result = node; - if (targetTypeId != baseTypeId) - result = (Node *) TypeConstraints(result, targetTypeId); - result = (Node *) makeRelabelType(result, targetTypeId, -1); - } else if (typeInheritsFrom(inputTypeId, targetTypeId)) { @@ -144,8 +157,8 @@ coerce_type(ParseState *pstate, Node *node, Oid inputTypeId, * * For domains, we use the coercion function for the base type. */ - Oid funcId; Oid baseTypeId = getBaseType(targetTypeId); + Oid funcId; funcId = find_coercion_function(baseTypeId, getBaseType(inputTypeId), @@ -157,11 +170,15 @@ coerce_type(ParseState *pstate, Node *node, Oid inputTypeId, result = build_func_call(funcId, baseTypeId, makeList1(node)); /* - * If domain, relabel with domain type ID and test against domain - * constraints + * If domain, test against domain constraints and relabel with + * domain type ID */ if (targetTypeId != baseTypeId) - result = (Node *) TypeConstraints(result, targetTypeId); + { + result = coerce_type_constraints(pstate, result, targetTypeId, + true); + result = (Node *) makeRelabelType(result, targetTypeId, -1); + } /* * If the input is a constant, apply the type conversion function @@ -306,28 +323,21 @@ can_coerce_type(int nargs, Oid *input_typeids, Oid *func_typeids, * function should be invoked to do that. * * "bpchar" (ie, char(N)) and "numeric" are examples of such types. + * + * This mechanism may seem pretty grotty and in need of replacement by + * something in pg_cast, but since typmod is only interesting for datatypes + * that have special handling in the grammar, there's not really much + * percentage in making it any easier to apply such coercions ... + * + * NOTE: this does not need to work on domain types, because any typmod + * coercion for a domain is considered to be part of the type coercion + * needed to produce the domain value in the first place. */ Node * coerce_type_typmod(ParseState *pstate, Node *node, Oid targetTypeId, int32 atttypmod) { - Oid baseTypeId; Oid funcId; - int32 domainTypMod; - - /* If given type is a domain, use base type instead */ - baseTypeId = getBaseTypeTypeMod(targetTypeId, &domainTypMod); - - - /* - * Use the domain typmod rather than what was supplied if the - * domain was empty. atttypmod will always be -1 if domains are in use. - */ - if (baseTypeId != targetTypeId) - { - Assert(atttypmod < 0); - atttypmod = domainTypMod; - } /* * A negative typmod is assumed to mean that no coercion is wanted. @@ -335,7 +345,8 @@ coerce_type_typmod(ParseState *pstate, Node *node, if (atttypmod < 0 || atttypmod == exprTypmod(node)) return node; - funcId = find_typmod_coercion_function(baseTypeId); + funcId = find_typmod_coercion_function(targetTypeId); + if (OidIsValid(funcId)) { Const *cons; @@ -348,7 +359,7 @@ coerce_type_typmod(ParseState *pstate, Node *node, false, false); - node = build_func_call(funcId, baseTypeId, makeList2(node, cons)); + node = build_func_call(funcId, targetTypeId, makeList2(node, cons)); } return node; @@ -869,12 +880,15 @@ build_func_call(Oid funcid, Oid rettype, List *args) /* * Create an expression tree to enforce the constraints (if any) - * which should be applied by the type. + * that should be applied by the type. Currently this is only + * interesting for domain types. */ -static Node * -TypeConstraints(Node *arg, Oid typeId) +Node * +coerce_type_constraints(ParseState *pstate, Node *arg, + Oid typeId, bool applyTypmod) { char *notNull = NULL; + int32 typmod = -1; for (;;) { @@ -885,12 +899,13 @@ TypeConstraints(Node *arg, Oid typeId) ObjectIdGetDatum(typeId), 0, 0, 0); if (!HeapTupleIsValid(tup)) - elog(ERROR, "getBaseType: failed to lookup type %u", typeId); + elog(ERROR, "coerce_type_constraints: failed to lookup type %u", + typeId); typTup = (Form_pg_type) GETSTRUCT(tup); /* Test for NOT NULL Constraint */ if (typTup->typnotnull && notNull == NULL) - notNull = NameStr(typTup->typname); + notNull = pstrdup(NameStr(typTup->typname)); /* TODO: Add CHECK Constraints to domains */ @@ -901,20 +916,32 @@ TypeConstraints(Node *arg, Oid typeId) break; } + Assert(typmod < 0); + typeId = typTup->typbasetype; + typmod = typTup->typtypmod; ReleaseSysCache(tup); } + /* + * If domain applies a typmod to its base type, do length coercion. + */ + if (applyTypmod && typmod >= 0) + arg = coerce_type_typmod(pstate, arg, typeId, typmod); + /* * Only need to add one NOT NULL check regardless of how many - * domains in the tree request it. + * domains in the stack request it. The topmost domain that + * requested it is used as the constraint name. */ - if (notNull != NULL) { - Constraint *r = makeNode(Constraint); + if (notNull) + { + ConstraintTest *r = makeNode(ConstraintTest); - r->raw_expr = arg; - r->contype = CONSTR_NOTNULL; - r->name = notNull; + r->arg = arg; + r->testtype = CONSTR_TEST_NOTNULL; + r->name = 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 1a7acd2252..de07d39b4d 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.126 2002/08/26 17:53:58 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.127 2002/08/31 22:10:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -640,6 +640,7 @@ transformExpr(ParseState *pstate, Node *expr) case T_ArrayRef: case T_FieldSelect: case T_RelabelType: + case T_ConstraintTest: { result = (Node *) expr; break; @@ -919,15 +920,15 @@ exprType(Node *expr) case T_CaseWhen: type = exprType(((CaseWhen *) expr)->result); break; - case T_Constraint: - type = exprType(((Constraint *) expr)->raw_expr); - break; case T_NullTest: type = BOOLOID; break; case T_BooleanTest: type = BOOLOID; break; + case T_ConstraintTest: + type = exprType(((ConstraintTest *) expr)->arg); + break; default: elog(ERROR, "exprType: Do not know how to get type for %d node", nodeTag(expr)); @@ -978,10 +979,8 @@ exprTypmod(Node *expr) break; case T_FieldSelect: return ((FieldSelect *) expr)->resulttypmod; - break; case T_RelabelType: return ((RelabelType *) expr)->resulttypmod; - break; case T_CaseExpr: { /* @@ -1013,6 +1012,9 @@ exprTypmod(Node *expr) return typmod; } break; + case T_ConstraintTest: + return exprTypmod(((ConstraintTest *) expr)->arg); + default: break; } diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c index 0eb29045b1..e75c193eff 100644 --- a/src/backend/parser/parse_type.c +++ b/src/backend/parser/parse_type.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_type.c,v 1.48 2002/08/08 01:22:35 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_type.c,v 1.49 2002/08/31 22:10:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -277,6 +277,16 @@ typeByVal(Type t) return typ->typbyval; } +/* given type (as type struct), return the value of its 'typtype' attribute.*/ +char +typeTypType(Type t) +{ + Form_pg_type typ; + + typ = (Form_pg_type) GETSTRUCT(t); + return typ->typtype; +} + /* given type (as type struct), return the name of type */ char * typeTypeName(Type t) @@ -434,7 +444,7 @@ parseTypeString(const char *str, Oid *type_id, int32 *typmod) * paranoia is justified since the string might contain anything. */ if (length(raw_parsetree_list) != 1) - elog(ERROR, "parseTypeString: Invalid type name '%s'", str); + elog(ERROR, "Invalid type name '%s'", str); stmt = (SelectStmt *) lfirst(raw_parsetree_list); if (stmt == NULL || !IsA(stmt, SelectStmt) || @@ -450,25 +460,26 @@ parseTypeString(const char *str, Oid *type_id, int32 *typmod) stmt->limitCount != NULL || stmt->forUpdate != NIL || stmt->op != SETOP_NONE) - elog(ERROR, "parseTypeString: Invalid type name '%s'", str); + elog(ERROR, "Invalid type name '%s'", str); if (length(stmt->targetList) != 1) - elog(ERROR, "parseTypeString: Invalid type name '%s'", str); + elog(ERROR, "Invalid type name '%s'", str); restarget = (ResTarget *) lfirst(stmt->targetList); if (restarget == NULL || !IsA(restarget, ResTarget) || restarget->name != NULL || restarget->indirection != NIL) - elog(ERROR, "parseTypeString: Invalid type name '%s'", str); + elog(ERROR, "Invalid type name '%s'", str); typecast = (TypeCast *) restarget->val; if (typecast == NULL || !IsA(typecast, TypeCast) || typecast->arg == NULL || !IsA(typecast->arg, A_Const)) - elog(ERROR, "parseTypeString: Invalid type name '%s'", str); + elog(ERROR, "Invalid type name '%s'", str); typename = typecast->typename; if (typename == NULL || !IsA(typename, TypeName)) - elog(ERROR, "parseTypeString: Invalid type name '%s'", str); + elog(ERROR, "Invalid type name '%s'", str); + *type_id = typenameTypeId(typename); *typmod = typename->typmod; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 458ab68749..c7da14ad7e 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.119 2002/08/29 01:19:41 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.120 2002/08/31 22:10:46 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -2187,6 +2187,18 @@ get_rule_expr(Node *node, deparse_context *context) } break; + case T_ConstraintTest: + { + ConstraintTest *ctest = (ConstraintTest *) node; + + /* + * We assume that the operations of the constraint node + * need not be explicitly represented in the output. + */ + get_rule_expr(ctest->arg, context); + } + break; + case T_SubLink: get_sublink_expr(node, context); break; diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 66dc58d6c4..4054b2920e 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/cache/lsyscache.c,v 1.81 2002/08/29 00:17:05 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/cache/lsyscache.c,v 1.82 2002/08/31 22:10:47 tgl Exp $ * * NOTES * Eventually, the index information should go through here, too. @@ -1074,12 +1074,12 @@ getBaseType(Oid typid) } /* - * getBaseTypeTypeMod - * If the given type is a domain, return its base type; - * otherwise return the type's own OID. Also return base typmod. + * getBaseTypeMod + * If the given type is a domain, return the typmod it applies to + * its base type; otherwise return the specified original typmod. */ -Oid -getBaseTypeTypeMod(Oid typid, int32 *typmod) +int32 +getBaseTypeMod(Oid typid, int32 typmod) { /* * We loop to find the bottom base type in a stack of domains. @@ -1093,7 +1093,7 @@ getBaseTypeTypeMod(Oid typid, int32 *typmod) ObjectIdGetDatum(typid), 0, 0, 0); if (!HeapTupleIsValid(tup)) - elog(ERROR, "getBaseTypeTypeMod: failed to lookup type %u", typid); + elog(ERROR, "getBaseTypeMod: failed to lookup type %u", typid); typTup = (Form_pg_type) GETSTRUCT(tup); if (typTup->typtype != 'd') { @@ -1102,12 +1102,20 @@ getBaseTypeTypeMod(Oid typid, int32 *typmod) break; } + /* + * The typmod applied to a domain should always be -1. + * + * We substitute the domain's typmod as we switch attention to + * the base type. + */ + Assert(typmod < 0); + typid = typTup->typbasetype; - *typmod = typTup->typtypmod; + typmod = typTup->typtypmod; ReleaseSysCache(tup); } - return typid; + return typmod; } /* diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 3f5f6d7449..ee472203e6 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.117 2002/08/27 04:55:11 tgl Exp $ + * $Id: nodes.h,v 1.118 2002/08/31 22:10:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -229,6 +229,7 @@ typedef enum NodeTag T_GroupClause, T_NullTest, T_BooleanTest, + T_ConstraintTest, T_CaseExpr, T_CaseWhen, T_FkConstraint, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 19d52d7217..a426aeba02 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.203 2002/08/30 19:23:20 tgl Exp $ + * $Id: parsenodes.h,v 1.204 2002/08/31 22:10:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -233,14 +233,13 @@ typedef struct NullTest NullTestType nulltesttype; /* IS NULL, IS NOT NULL */ } NullTest; -/* ---------------- +/* * BooleanTest * * BooleanTest represents the operation of determining whether a boolean * is TRUE, FALSE, or UNKNOWN (ie, NULL). All six meaningful combinations * are supported. Note that a NULL input does *not* cause a NULL result. * The appropriate test is performed and returned as a boolean Datum. - * ---------------- */ typedef enum BoolTestType @@ -255,6 +254,29 @@ typedef struct BooleanTest BoolTestType booltesttype; /* test type */ } BooleanTest; +/* + * ConstraintTest + * + * ConstraintTest represents the operation of testing a value to see whether + * it meets a constraint. If so, the input value is returned as the result; + * if not, an error is raised. + */ + +typedef enum ConstraintTestType +{ + CONSTR_TEST_NOTNULL, + CONSTR_TEST_CHECK +} ConstraintTestType; + +typedef struct ConstraintTest +{ + NodeTag type; + Node *arg; /* input expression */ + ConstraintTestType testtype; /* test type */ + char *name; /* name of constraint (for error msgs) */ + Node *check_expr; /* for CHECK test, a boolean expression */ +} ConstraintTest; + /* * ColumnDef - column definition (used in various creates) * diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h index e306493fa3..328332aafd 100644 --- a/src/include/parser/parse_coerce.h +++ b/src/include/parser/parse_coerce.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_coerce.h,v 1.44 2002/06/20 20:29:51 momjian Exp $ + * $Id: parse_coerce.h,v 1.45 2002/08/31 22:10:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -44,6 +44,8 @@ extern Node *coerce_type(ParseState *pstate, Node *node, Oid inputTypeId, Oid targetTypeId, int32 atttypmod, bool isExplicit); extern Node *coerce_type_typmod(ParseState *pstate, Node *node, Oid targetTypeId, int32 atttypmod); +extern Node *coerce_type_constraints(ParseState *pstate, Node *arg, + Oid typeId, bool applyTypmod); extern Node *coerce_to_boolean(Node *node, const char *constructName); diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h index f348a32d41..86c453882b 100644 --- a/src/include/parser/parse_type.h +++ b/src/include/parser/parse_type.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_type.h,v 1.23 2002/06/20 20:29:52 momjian Exp $ + * $Id: parse_type.h,v 1.24 2002/08/31 22:10:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -31,6 +31,7 @@ extern Type typeidType(Oid id); extern Oid typeTypeId(Type tp); extern int16 typeLen(Type t); extern bool typeByVal(Type t); +extern char typeTypType(Type t); extern char *typeTypeName(Type t); extern char typeTypeFlag(Type t); extern Oid typeTypeRelid(Type typ); diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 2681d67139..3c442bbd20 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: lsyscache.h,v 1.60 2002/08/29 00:17:06 tgl Exp $ + * $Id: lsyscache.h,v 1.61 2002/08/31 22:10:48 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -58,7 +58,7 @@ extern void getTypeInputInfo(Oid type, Oid *typInput, Oid *typElem); extern bool getTypeOutputInfo(Oid type, Oid *typOutput, Oid *typElem, bool *typIsVarlena); extern Oid getBaseType(Oid typid); -extern Oid getBaseTypeTypeMod(Oid typid, int32 *typmod); +extern int32 getBaseTypeMod(Oid typid, int32 typmod); extern int32 get_typavgwidth(Oid typid, int32 typmod); extern int32 get_attavgwidth(Oid relid, AttrNumber attnum); extern bool get_attstatsslot(HeapTuple statstuple, diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out index a67559129c..522c28121f 100644 --- a/src/test/regress/expected/domain.out +++ b/src/test/regress/expected/domain.out @@ -41,8 +41,7 @@ select * from basictest; (2 rows) -- check that domains inherit operations from base types --- XXX shouldn't have to quote the constant here -select testtext || testvarchar as concat, testnumeric + '42' as sum +select testtext || testvarchar as concat, testnumeric + 42 as sum from basictest; concat | sum -----------+-------- @@ -99,7 +98,7 @@ create table nulltest , col4 dnull ); INSERT INTO nulltest DEFAULT VALUES; -ERROR: ExecInsert: Fail to add null value in not null attribute col3 +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'); ERROR: Domain dnotnull does not allow NULL values @@ -147,7 +146,7 @@ create table defaulttest , col5 ddef1 NOT NULL DEFAULT NULL , col6 ddef2 DEFAULT '88' , col7 ddef4 DEFAULT 8000 - , col8 ddef5 + , col8 ddef5 ); NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index 'defaulttest_pkey' for table 'defaulttest' insert into defaulttest default values; diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql index 4c2e7e31ac..9627870934 100644 --- a/src/test/regress/sql/domain.sql +++ b/src/test/regress/sql/domain.sql @@ -38,8 +38,7 @@ INSERT INTO basictest values ('88', 'haha', 'short', '123.1212'); -- Truncate select * from basictest; -- check that domains inherit operations from base types --- XXX shouldn't have to quote the constant here -select testtext || testvarchar as concat, testnumeric + '42' as sum +select testtext || testvarchar as concat, testnumeric + 42 as sum from basictest; drop table basictest; -- 2.40.0