From 7fdb4305db20f64bce27e6bac0a0f9c972e4dec8 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 5 Apr 2006 22:11:58 +0000 Subject: [PATCH] Fix a bunch of problems with domains by making them use special input functions that apply the necessary domain constraint checks immediately. This fixes cases where domain constraints went unchecked for statement parameters, PL function local variables and results, etc. We can also eliminate existing special cases for domains in places that had gotten it right, eg COPY. Also, allow domains over domains (base of a domain is another domain type). This almost worked before, but was disallowed because the original patch hadn't gotten it quite right. --- doc/src/sgml/ref/create_domain.sgml | 45 ++-- doc/src/sgml/ref/create_type.sgml | 3 +- src/backend/access/common/printtup.c | 10 +- src/backend/commands/copy.c | 67 +----- src/backend/commands/typecmds.c | 28 +-- src/backend/optimizer/prep/preptlist.c | 4 +- src/backend/parser/parse_coerce.c | 60 +++-- src/backend/rewrite/rewriteHandler.c | 4 +- src/backend/rewrite/rewriteManip.c | 4 +- src/backend/utils/adt/Makefile | 5 +- src/backend/utils/adt/domains.c | 297 +++++++++++++++++++++++++ src/backend/utils/cache/lsyscache.c | 49 ++-- src/include/catalog/catversion.h | 4 +- src/include/catalog/pg_proc.h | 6 +- src/include/catalog/pg_type.h | 4 +- src/include/parser/parse_coerce.h | 5 +- src/include/utils/builtins.h | 6 +- src/include/utils/lsyscache.h | 4 +- src/test/regress/expected/domain.out | 84 ++++++- src/test/regress/sql/domain.sql | 56 ++++- 20 files changed, 549 insertions(+), 196 deletions(-) create mode 100644 src/backend/utils/adt/domains.c diff --git a/doc/src/sgml/ref/create_domain.sgml b/doc/src/sgml/ref/create_domain.sgml index 4beb0ae53b..bfed902739 100644 --- a/doc/src/sgml/ref/create_domain.sgml +++ b/doc/src/sgml/ref/create_domain.sgml @@ -1,5 +1,5 @@ @@ -35,8 +35,10 @@ where constraint is: Description - CREATE DOMAIN creates a new data domain. The - user who defines a domain becomes its owner. + CREATE DOMAIN creates a new domain. A domain is + essentially a data type with optional constraints (restrictions on + the allowed set of values). + The user who defines a domain becomes its owner. @@ -48,24 +50,13 @@ where constraint is: - Domains are useful for abstracting common fields between tables - into a single location for maintenance. For example, an email address - column may be used in several tables, all with the same properties. - Define a domain and use that rather than setting up each table's - constraints individually. + Domains are useful for abstracting common constraints on fields into + a single location for maintenance. For example, several tables might + contain email address columns, all requiring the same CHECK constraint + to verify the address syntax. + Define a domain rather than setting up each table's constraint + individually. - - - - At present, declaring a function result value as a domain - is pretty dangerous, because none of the procedural languages enforce domain constraints - on their results. You'll need to make sure that the function code itself - respects the constraints. In PL/pgSQL, one possible - workaround is to explicitly cast the result value to the domain type - when you return it. PL/pgSQL does not enforce domain - constraints for local variables within functions, either. - - @@ -156,7 +147,7 @@ where constraint is: CHECK clauses specify integrity constraints or tests which values of the domain must satisfy. Each constraint must be an expression - producing a Boolean result. It should use the name VALUE + producing a Boolean result. It should use the key word VALUE to refer to the value being tested. @@ -185,12 +176,12 @@ OR VALUE ~ '^\\d{5}-\\d{4}$' ); CREATE TABLE us_snail_addy ( - address_id SERIAL PRIMARY KEY -, street1 TEXT NOT NULL -, street2 TEXT -, street3 TEXT -, city TEXT NOT NULL -, postal us_postal_code NOT NULL + address_id SERIAL PRIMARY KEY, + street1 TEXT NOT NULL, + street2 TEXT, + street3 TEXT, + city TEXT NOT NULL, + postal us_postal_code NOT NULL ); diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index e3b8b44d8f..68ec242ae5 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -1,5 +1,5 @@ @@ -591,6 +591,7 @@ CREATE TABLE big_objs ( + diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c index ba5793b0e7..7eb46bb6ec 100644 --- a/src/backend/access/common/printtup.c +++ b/src/backend/access/common/printtup.c @@ -9,7 +9,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/common/printtup.c,v 1.95 2006/04/04 19:35:33 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/access/common/printtup.c,v 1.96 2006/04/05 22:11:54 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -177,7 +177,6 @@ SendRowDescriptionMessage(TupleDesc typeinfo, List *targetlist, int16 *formats) { Oid atttypid = attrs[i]->atttypid; int32 atttypmod = attrs[i]->atttypmod; - Oid basetype; pq_sendstring(&buf, NameStr(attrs[i]->attname)); /* column ID info appears in protocol 3.0 and up */ @@ -203,12 +202,7 @@ SendRowDescriptionMessage(TupleDesc typeinfo, List *targetlist, int16 *formats) } } /* If column is a domain, send the base type and typmod instead */ - basetype = getBaseType(atttypid); - if (basetype != atttypid) - { - atttypmod = get_typtypmod(atttypid); - atttypid = basetype; - } + atttypid = getBaseTypeAndTypmod(atttypid, &atttypmod); pq_sendint(&buf, (int) atttypid, sizeof(atttypid)); pq_sendint(&buf, attrs[i]->attlen, sizeof(attrs[i]->attlen)); /* typmod appears in protocol 2.0 and up */ diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index af21d565f1..2b49094bea 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.262 2006/04/04 19:35:33 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.263 2006/04/05 22:11:54 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1545,9 +1545,7 @@ CopyFrom(CopyState cstate) FmgrInfo oid_in_function; Oid *typioparams; Oid oid_typioparam; - ExprState **constraintexprs; bool *force_notnull; - bool hasConstraints = false; int attnum; int i; Oid in_func_oid; @@ -1608,7 +1606,6 @@ CopyFrom(CopyState cstate) typioparams = (Oid *) palloc(num_phys_attrs * sizeof(Oid)); defmap = (int *) palloc(num_phys_attrs * sizeof(int)); defexprs = (ExprState **) palloc(num_phys_attrs * sizeof(ExprState *)); - constraintexprs = (ExprState **) palloc0(num_phys_attrs * sizeof(ExprState *)); force_notnull = (bool *) palloc(num_phys_attrs * sizeof(bool)); for (attnum = 1; attnum <= num_phys_attrs; attnum++) @@ -1646,35 +1643,6 @@ CopyFrom(CopyState cstate) num_defaults++; } } - - /* If it's a domain type, set up to check domain constraints */ - if (get_typtype(attr[attnum - 1]->atttypid) == 'd') - { - Param *prm; - Node *node; - - /* - * Easiest way to do this is to use parse_coerce.c to set up an - * expression that checks the constraints. (At present, the - * expression might contain a length-coercion-function call and/or - * CoerceToDomain nodes.) The bottom of the expression is a Param - * node so that we can fill in the actual datum during the data - * input loop. - */ - prm = makeNode(Param); - prm->paramkind = PARAM_EXEC; - prm->paramid = 0; - prm->paramtype = getBaseType(attr[attnum - 1]->atttypid); - - node = coerce_to_domain((Node *) prm, - prm->paramtype, - attr[attnum - 1]->atttypid, - COERCE_IMPLICIT_CAST, false, false); - - constraintexprs[attnum - 1] = ExecPrepareExpr((Expr *) node, - estate); - hasConstraints = true; - } } /* Prepare to catch AFTER triggers. */ @@ -1743,11 +1711,6 @@ CopyFrom(CopyState cstate) nfields = file_has_oids ? (attr_count + 1) : attr_count; field_strings = (char **) palloc(nfields * sizeof(char *)); - /* Make room for a PARAM_EXEC value for domain constraint checks */ - if (hasConstraints) - econtext->ecxt_param_exec_vals = (ParamExecData *) - palloc0(sizeof(ParamExecData)); - /* Initialize state variables */ cstate->fe_eof = false; cstate->eol_type = EOL_UNKNOWN; @@ -1942,33 +1905,6 @@ CopyFrom(CopyState cstate) nulls[defmap[i]] = ' '; } - /* Next apply any domain constraints */ - if (hasConstraints) - { - ParamExecData *prmdata = &econtext->ecxt_param_exec_vals[0]; - - for (i = 0; i < num_phys_attrs; i++) - { - ExprState *exprstate = constraintexprs[i]; - - if (exprstate == NULL) - continue; /* no constraint for this attr */ - - /* Insert current row's value into the Param value */ - prmdata->value = values[i]; - prmdata->isnull = (nulls[i] == 'n'); - - /* - * Execute the constraint expression. Allow the expression to - * replace the value (consider e.g. a timestamp precision - * restriction). - */ - values[i] = ExecEvalExpr(exprstate, econtext, - &isnull, NULL); - nulls[i] = isnull ? 'n' : ' '; - } - } - /* And now we can form the input tuple. */ tuple = heap_formtuple(tupDesc, values, nulls); @@ -2043,7 +1979,6 @@ CopyFrom(CopyState cstate) pfree(typioparams); pfree(defmap); pfree(defexprs); - pfree(constraintexprs); pfree(force_notnull); ExecDropSingleTupleTableSlot(slot); diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 83143496db..21546d1c8b 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.89 2006/03/14 22:48:18 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.90 2006/04/05 22:11:55 tgl Exp $ * * DESCRIPTION * The "DefineFoo" routines take the parse tree and pick out the @@ -536,6 +536,7 @@ DefineDomain(CreateDomainStmt *stmt) Oid sendProcedure; Oid analyzeProcedure; bool byValue; + Oid typelem; char delimiter; char alignment; char storage; @@ -547,7 +548,6 @@ DefineDomain(CreateDomainStmt *stmt) char *defaultValueBin = NULL; bool typNotNull = false; bool nullDefined = false; - Oid basetypelem; int32 typNDims = list_length(stmt->typename->arrayBounds); HeapTuple typeTup; List *schema = stmt->constraints; @@ -589,12 +589,12 @@ DefineDomain(CreateDomainStmt *stmt) basetypeoid = HeapTupleGetOid(typeTup); /* - * Base type must be a plain base type. Domains over pseudo types would - * create a security hole. Domains of domains might be made to work in - * the future, but not today. Ditto for domains over complex types. + * Base type must be a plain base type or another domain. Domains over + * pseudotypes would create a security hole. Domains over composite + * types might be made to work in the future, but not today. */ typtype = baseType->typtype; - if (typtype != 'b') + if (typtype != 'b' && typtype != 'd') ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("\"%s\" is not a valid base type for a domain", @@ -612,13 +612,16 @@ DefineDomain(CreateDomainStmt *stmt) /* Storage Length */ internalLength = baseType->typlen; + /* Array element type (in case base type is an array) */ + typelem = baseType->typelem; + /* Array element Delimiter */ delimiter = baseType->typdelim; /* I/O Functions */ - inputProcedure = baseType->typinput; + inputProcedure = F_DOMAIN_IN; outputProcedure = baseType->typoutput; - receiveProcedure = baseType->typreceive; + receiveProcedure = F_DOMAIN_RECV; sendProcedure = baseType->typsend; /* Analysis function */ @@ -636,13 +639,6 @@ DefineDomain(CreateDomainStmt *stmt) if (!isnull) defaultValueBin = DatumGetCString(DirectFunctionCall1(textout, datum)); - /* - * Pull out the typelem name of the parent OID. - * - * This is what enables us to make a domain of an array - */ - basetypelem = baseType->typelem; - /* * Run through constraints manually to avoid the additional processing * conducted by DefineRelation() and friends. @@ -776,7 +772,7 @@ DefineDomain(CreateDomainStmt *stmt) receiveProcedure, /* receive procedure */ sendProcedure, /* send procedure */ analyzeProcedure, /* analyze procedure */ - basetypelem, /* element type ID */ + typelem, /* element type ID */ basetypeoid, /* base type ID */ defaultValue, /* default type value (text) */ defaultValueBin, /* default type value (binary) */ diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 436d6fdce0..5a37b86eb5 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 - * $PostgreSQL: pgsql/src/backend/optimizer/prep/preptlist.c,v 1.80 2006/03/05 15:58:31 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/prep/preptlist.c,v 1.81 2006/04/05 22:11:55 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -249,7 +249,7 @@ expand_targetlist(List *tlist, int command_type, true, /* isnull */ att_tup->attbyval); new_expr = coerce_to_domain(new_expr, - InvalidOid, + InvalidOid, -1, atttype, COERCE_IMPLICIT_CAST, false, diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 5a343e768d..98393a54aa 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.136 2006/04/04 19:35:34 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.137 2006/04/05 22:11:55 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -158,10 +158,24 @@ coerce_type(ParseState *pstate, Node *node, */ Const *con = (Const *) node; Const *newcon = makeNode(Const); - Type targetType = typeidType(targetTypeId); - char targetTyptype = typeTypType(targetType); + Oid baseTypeId; + int32 baseTypeMod; + Type targetType; - newcon->consttype = targetTypeId; + /* + * If the target type is a domain, we want to call its base type's + * input routine, not domain_in(). This is to avoid premature + * failure when the domain applies a typmod: existing input + * routines follow implicit-coercion semantics for length checks, + * which is not always what we want here. The needed check will + * be applied properly inside coerce_to_domain(). + */ + baseTypeMod = -1; + baseTypeId = getBaseTypeAndTypmod(targetTypeId, &baseTypeMod); + + targetType = typeidType(baseTypeId); + + newcon->consttype = baseTypeId; newcon->constlen = typeLen(targetType); newcon->constbyval = typeByVal(targetType); newcon->constisnull = con->constisnull; @@ -185,8 +199,10 @@ coerce_type(ParseState *pstate, Node *node, result = (Node *) newcon; /* If target is a domain, apply constraints. */ - if (targetTyptype == 'd') - result = coerce_to_domain(result, InvalidOid, targetTypeId, + if (baseTypeId != targetTypeId) + result = coerce_to_domain(result, + baseTypeId, baseTypeMod, + targetTypeId, cformat, false, false); ReleaseSysCache(targetType); @@ -239,9 +255,7 @@ coerce_type(ParseState *pstate, Node *node, param->paramtype = targetTypeId; - /* Apply domain constraints, if necessary */ - return coerce_to_domain((Node *) param, InvalidOid, targetTypeId, - cformat, false, false); + return (Node *) param; } if (find_coercion_pathway(targetTypeId, inputTypeId, ccontext, &funcId)) @@ -255,13 +269,11 @@ coerce_type(ParseState *pstate, Node *node, * and we need to extract the correct typmod to use from the * domain's typtypmod. */ - Oid baseTypeId = getBaseType(targetTypeId); + Oid baseTypeId; int32 baseTypeMod; - if (targetTypeId != baseTypeId) - baseTypeMod = get_typtypmod(targetTypeId); - else - baseTypeMod = targetTypeMod; + baseTypeMod = targetTypeMod; + baseTypeId = getBaseTypeAndTypmod(targetTypeId, &baseTypeMod); result = build_coercion_expression(node, funcId, baseTypeId, baseTypeMod, @@ -274,7 +286,8 @@ coerce_type(ParseState *pstate, Node *node, * selected coercion function was a type-and-length coercion. */ if (targetTypeId != baseTypeId) - result = coerce_to_domain(result, baseTypeId, targetTypeId, + result = coerce_to_domain(result, baseTypeId, baseTypeMod, + targetTypeId, cformat, true, exprIsLengthCoercion(result, NULL)); @@ -290,7 +303,7 @@ coerce_type(ParseState *pstate, Node *node, * that must be accounted for. If the destination is a domain * then we won't need a RelabelType node. */ - result = coerce_to_domain(node, InvalidOid, targetTypeId, + result = coerce_to_domain(node, InvalidOid, -1, targetTypeId, cformat, false, false); if (result == node) { @@ -439,15 +452,18 @@ can_coerce_type(int nargs, Oid *input_typeids, Oid *target_typeids, * 'arg': input expression * 'baseTypeId': base type of domain, if known (pass InvalidOid if caller * has not bothered to look this up) + * 'baseTypeMod': base type typmod of domain, if known (pass -1 if caller + * has not bothered to look this up) * 'typeId': target type to coerce to * 'cformat': coercion format * 'hideInputCoercion': if true, hide the input coercion under this one. - * 'lengthCoercionDone': if true, caller already accounted for length. + * 'lengthCoercionDone': if true, caller already accounted for length, + * ie the input is already of baseTypMod as well as baseTypeId. * * If the target type isn't a domain, the given 'arg' is returned as-is. */ Node * -coerce_to_domain(Node *arg, Oid baseTypeId, Oid typeId, +coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId, CoercionForm cformat, bool hideInputCoercion, bool lengthCoercionDone) { @@ -455,7 +471,7 @@ coerce_to_domain(Node *arg, Oid baseTypeId, Oid typeId, /* Get the base type if it hasn't been supplied */ if (baseTypeId == InvalidOid) - baseTypeId = getBaseType(typeId); + baseTypeId = getBaseTypeAndTypmod(typeId, &baseTypeMod); /* If it isn't a domain, return the node as it was passed in */ if (baseTypeId == typeId) @@ -480,10 +496,8 @@ coerce_to_domain(Node *arg, Oid baseTypeId, Oid typeId, */ if (!lengthCoercionDone) { - int32 typmod = get_typtypmod(typeId); - - if (typmod >= 0) - arg = coerce_type_typmod(arg, baseTypeId, typmod, + if (baseTypeMod >= 0) + arg = coerce_type_typmod(arg, baseTypeId, baseTypeMod, COERCE_IMPLICIT_CAST, (cformat != COERCE_IMPLICIT_CAST), false); diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index acebefca3f..6d1ace66f1 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.161 2006/03/05 15:58:36 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.162 2006/04/05 22:11:55 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -599,7 +599,7 @@ rewriteTargetList(Query *parsetree, Relation target_relation) att_tup->attbyval); /* this is to catch a NOT NULL domain constraint */ new_expr = coerce_to_domain(new_expr, - InvalidOid, + InvalidOid, -1, att_tup->atttypid, COERCE_IMPLICIT_CAST, false, diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c index d7cd8de677..175df7a695 100644 --- a/src/backend/rewrite/rewriteManip.c +++ b/src/backend/rewrite/rewriteManip.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/rewrite/rewriteManip.c,v 1.97 2006/03/05 15:58:36 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/rewrite/rewriteManip.c,v 1.98 2006/04/05 22:11:55 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -860,7 +860,7 @@ resolve_one_var(Var *var, ResolveNew_context *context) /* Otherwise replace unmatched var with a null */ /* need coerce_to_domain in case of NOT NULL domain constraint */ return coerce_to_domain((Node *) makeNullConst(var->vartype), - InvalidOid, + InvalidOid, -1, var->vartype, COERCE_IMPLICIT_CAST, false, diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 294fd9de49..5a1996c343 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -1,7 +1,7 @@ # # Makefile for utils/adt # -# $PostgreSQL: pgsql/src/backend/utils/adt/Makefile,v 1.59 2005/08/12 03:24:08 momjian Exp $ +# $PostgreSQL: pgsql/src/backend/utils/adt/Makefile,v 1.60 2006/04/05 22:11:55 tgl Exp $ # subdir = src/backend/utils/adt @@ -16,7 +16,8 @@ endif endif OBJS = acl.o arrayfuncs.o array_userfuncs.o arrayutils.o bool.o \ - cash.o char.o date.o datetime.o datum.o float.o format_type.o \ + cash.o char.o date.o datetime.o datum.o domains.o \ + float.o format_type.o \ geo_ops.o geo_selfuncs.o int.o int8.o like.o lockfuncs.o \ misc.o nabstime.o name.o not_in.o numeric.o numutils.o \ oid.o oracle_compat.o pseudotypes.o rowtypes.o \ diff --git a/src/backend/utils/adt/domains.c b/src/backend/utils/adt/domains.c new file mode 100644 index 0000000000..051145f237 --- /dev/null +++ b/src/backend/utils/adt/domains.c @@ -0,0 +1,297 @@ +/*------------------------------------------------------------------------- + * + * domains.c + * I/O functions for domain types. + * + * The output functions for a domain type are just the same ones provided + * by its underlying base type. The input functions, however, must be + * prepared to apply any constraints defined by the type. So, we create + * special input functions that invoke the base type's input function + * and then check the constraints. + * + * The overhead required for constraint checking can be high, since examining + * the catalogs to discover the constraints for a given domain is not cheap. + * We have two mechanisms for minimizing this cost: + * 1. In a nest of domains, we flatten the checking of all the levels + * into just one operation. + * 2. We cache the list of constraint items in the FmgrInfo struct + * passed by the caller. + * + * We also have to create an EState to evaluate CHECK expressions in. + * Creating and destroying an EState is somewhat expensive, and so it's + * tempting to cache the EState too. However, that would mean that the + * EState never gets an explicit FreeExecutorState call, which is a bad idea + * because it risks leaking non-memory resources. + * + * + * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $PostgreSQL: pgsql/src/backend/utils/adt/domains.c,v 1.1 2006/04/05 22:11:55 tgl Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "commands/typecmds.h" +#include "executor/executor.h" +#include "lib/stringinfo.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" + + +/* + * structure to cache state across multiple calls + */ +typedef struct DomainIOData +{ + Oid domain_type; + /* Data needed to call base type's input function */ + Oid typiofunc; + Oid typioparam; + int32 typtypmod; + FmgrInfo proc; + /* List of constraint items to check */ + List *constraint_list; +} DomainIOData; + + +/* + * domain_state_setup - initialize the cache for a new domain type. + */ +static void +domain_state_setup(DomainIOData *my_extra, Oid domainType, bool binary, + MemoryContext mcxt) +{ + Oid baseType; + MemoryContext oldcontext; + + /* Mark cache invalid */ + my_extra->domain_type = InvalidOid; + + /* Find out the base type */ + my_extra->typtypmod = -1; + baseType = getBaseTypeAndTypmod(domainType, &my_extra->typtypmod); + if (baseType == domainType) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("type %s is not a domain", + format_type_be(domainType)))); + + /* Look up underlying I/O function */ + if (binary) + getTypeBinaryInputInfo(baseType, + &my_extra->typiofunc, + &my_extra->typioparam); + else + getTypeInputInfo(baseType, + &my_extra->typiofunc, + &my_extra->typioparam); + fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, mcxt); + + /* Look up constraints for domain */ + oldcontext = MemoryContextSwitchTo(mcxt); + my_extra->constraint_list = GetDomainConstraints(domainType); + MemoryContextSwitchTo(oldcontext); + + /* Mark cache valid */ + my_extra->domain_type = domainType; +} + +/* + * domain_check_input - apply the cached checks. + * + * This is extremely similar to ExecEvalCoerceToDomain in execQual.c. + */ +static void +domain_check_input(Datum value, bool isnull, DomainIOData *my_extra) +{ + EState *estate = NULL; + ListCell *l; + + foreach(l, my_extra->constraint_list) + { + DomainConstraintState *con = (DomainConstraintState *) lfirst(l); + + switch (con->constrainttype) + { + case DOM_CONSTRAINT_NOTNULL: + if (isnull) + ereport(ERROR, + (errcode(ERRCODE_NOT_NULL_VIOLATION), + errmsg("domain %s does not allow null values", + format_type_be(my_extra->domain_type)))); + break; + case DOM_CONSTRAINT_CHECK: + { + ExprContext *econtext; + Datum conResult; + bool conIsNull; + Datum save_datum; + bool save_isNull; + + if (estate == NULL) + estate = CreateExecutorState(); + econtext = GetPerTupleExprContext(estate); + + /* + * Set up value to be returned by CoerceToDomainValue + * nodes. We must save and restore prior setting of + * econtext's domainValue fields, in case this node is + * itself within a check expression for another domain. + */ + save_datum = econtext->domainValue_datum; + save_isNull = econtext->domainValue_isNull; + + econtext->domainValue_datum = value; + econtext->domainValue_isNull = isnull; + + conResult = ExecEvalExprSwitchContext(con->check_expr, + econtext, + &conIsNull, NULL); + + if (!conIsNull && + !DatumGetBool(conResult)) + ereport(ERROR, + (errcode(ERRCODE_CHECK_VIOLATION), + errmsg("value for domain %s violates check constraint \"%s\"", + format_type_be(my_extra->domain_type), + con->name))); + econtext->domainValue_datum = save_datum; + econtext->domainValue_isNull = save_isNull; + + break; + } + default: + elog(ERROR, "unrecognized constraint type: %d", + (int) con->constrainttype); + break; + } + } + + if (estate) + FreeExecutorState(estate); +} + + +/* + * domain_in - input routine for any domain type. + */ +Datum +domain_in(PG_FUNCTION_ARGS) +{ + char *string; + Oid domainType; + DomainIOData *my_extra; + Datum value; + + /* + * Since domain_in is not strict, we have to check for null inputs. + * The typioparam argument should never be null in normal system usage, + * but it could be null in a manual invocation --- if so, just return null. + */ + if (PG_ARGISNULL(0)) + string = NULL; + else + string = PG_GETARG_CSTRING(0); + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + domainType = PG_GETARG_OID(1); + + /* + * We arrange to look up the needed info just once per series of + * calls, assuming the domain type doesn't change underneath us. + */ + my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL) + { + my_extra = (DomainIOData *) MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(DomainIOData)); + domain_state_setup(my_extra, domainType, false, + fcinfo->flinfo->fn_mcxt); + fcinfo->flinfo->fn_extra = (void *) my_extra; + } + else if (my_extra->domain_type != domainType) + domain_state_setup(my_extra, domainType, false, + fcinfo->flinfo->fn_mcxt); + + /* + * Invoke the base type's typinput procedure to convert the data. + */ + value = InputFunctionCall(&my_extra->proc, + string, + my_extra->typioparam, + my_extra->typtypmod); + + /* + * Do the necessary checks to ensure it's a valid domain value. + */ + domain_check_input(value, (string == NULL), my_extra); + + if (string == NULL) + PG_RETURN_NULL(); + else + PG_RETURN_DATUM(value); +} + +/* + * domain_recv - binary input routine for any domain type. + */ +Datum +domain_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf; + Oid domainType; + DomainIOData *my_extra; + Datum value; + + /* + * Since domain_recv is not strict, we have to check for null inputs. + * The typioparam argument should never be null in normal system usage, + * but it could be null in a manual invocation --- if so, just return null. + */ + if (PG_ARGISNULL(0)) + buf = NULL; + else + buf = (StringInfo) PG_GETARG_POINTER(0); + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + domainType = PG_GETARG_OID(1); + + /* + * We arrange to look up the needed info just once per series of + * calls, assuming the domain type doesn't change underneath us. + */ + my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL) + { + my_extra = (DomainIOData *) MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(DomainIOData)); + domain_state_setup(my_extra, domainType, true, + fcinfo->flinfo->fn_mcxt); + fcinfo->flinfo->fn_extra = (void *) my_extra; + } + else if (my_extra->domain_type != domainType) + domain_state_setup(my_extra, domainType, true, + fcinfo->flinfo->fn_mcxt); + + /* + * Invoke the base type's typreceive procedure to convert the data. + */ + value = ReceiveFunctionCall(&my_extra->proc, + buf, + my_extra->typioparam, + my_extra->typtypmod); + + /* + * Do the necessary checks to ensure it's a valid domain value. + */ + domain_check_input(value, (buf == NULL), my_extra); + + if (buf == NULL) + PG_RETURN_NULL(); + else + PG_RETURN_DATUM(value); +} diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index fb24cc6236..a52366c534 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 - * $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.133 2006/04/04 19:35:36 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.134 2006/04/05 22:11:55 tgl Exp $ * * NOTES * Eventually, the index information should go through here, too. @@ -1469,33 +1469,6 @@ get_typstorage(Oid typid) return 'p'; } -/* - * get_typtypmod - * - * Given the type OID, return the typtypmod field (domain's typmod - * for base type) - */ -int32 -get_typtypmod(Oid typid) -{ - HeapTuple tp; - - tp = SearchSysCache(TYPEOID, - ObjectIdGetDatum(typid), - 0, 0, 0); - if (HeapTupleIsValid(tp)) - { - Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); - int32 result; - - result = typtup->typtypmod; - ReleaseSysCache(tp); - return result; - } - else - return -1; -} - /* * get_typdefault * Given a type OID, return the type's default value, if any. @@ -1583,6 +1556,23 @@ get_typdefault(Oid typid) */ Oid getBaseType(Oid typid) +{ + int32 typmod = -1; + + return getBaseTypeAndTypmod(typid, &typmod); +} + +/* + * getBaseTypeAndTypmod + * If the given type is a domain, return its base type and typmod; + * otherwise return the type's own OID, and leave *typmod unchanged. + * + * Note that the "applied typmod" should be -1 for every domain level + * above the bottommost; therefore, if the passed-in typid is indeed + * a domain, *typmod should be -1. + */ +Oid +getBaseTypeAndTypmod(Oid typid, int32 *typmod) { /* * We loop to find the bottom base type in a stack of domains. @@ -1605,7 +1595,10 @@ getBaseType(Oid typid) break; } + Assert(*typmod == -1); typid = typTup->typbasetype; + *typmod = typTup->typtypmod; + ReleaseSysCache(tup); } diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 2ae52c723f..6b59dd55fe 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.321 2006/03/16 00:31:55 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.322 2006/04/05 22:11:55 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200603151 +#define CATALOG_VERSION_NO 200604051 #endif diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index d8fda0b8c3..9542d632f2 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.404 2006/03/10 20:15:26 neilc Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.405 2006/04/05 22:11:55 tgl Exp $ * * NOTES * The script catalog/genbki.sh reads this file and generates .bki @@ -3363,6 +3363,10 @@ DATA(insert OID = 2398 ( shell_in PGNSP PGUID 12 f f t f i 1 2282 "2275" _nul DESCR("I/O"); DATA(insert OID = 2399 ( shell_out PGNSP PGUID 12 f f t f i 1 2275 "2282" _null_ _null_ _null_ shell_out - _null_ )); DESCR("I/O"); +DATA(insert OID = 2597 ( domain_in PGNSP PGUID 12 f f f f v 3 2276 "2275 26 23" _null_ _null_ _null_ domain_in - _null_ )); +DESCR("I/O"); +DATA(insert OID = 2598 ( domain_recv PGNSP PGUID 12 f f f f v 3 2276 "2281 26 23" _null_ _null_ _null_ domain_recv - _null_ )); +DESCR("I/O"); /* cryptographic */ DATA(insert OID = 2311 ( md5 PGNSP PGUID 12 f f t f i 1 25 "25" _null_ _null_ _null_ md5_text - _null_ )); diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index 00b7bf3dc3..b9aeec1e3c 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_type.h,v 1.170 2006/03/05 15:58:55 momjian Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_type.h,v 1.171 2006/04/05 22:11:57 tgl Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -156,7 +156,7 @@ CATALOG(pg_type,1247) BKI_BOOTSTRAP bool typnotnull; /* - * Domains use typbasetype to show the base (or complex) type that the + * Domains use typbasetype to show the base (or domain) type that the * domain is based on. Zero if the type is not a domain. */ Oid typbasetype; diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h index 871c82892d..70b7d07489 100644 --- a/src/include/parser/parse_coerce.h +++ b/src/include/parser/parse_coerce.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/parser/parse_coerce.h,v 1.61 2006/03/05 15:58:57 momjian Exp $ + * $PostgreSQL: pgsql/src/include/parser/parse_coerce.h,v 1.62 2006/04/05 22:11:57 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -49,7 +49,8 @@ extern bool can_coerce_type(int nargs, Oid *input_typeids, Oid *target_typeids, extern Node *coerce_type(ParseState *pstate, Node *node, Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod, CoercionContext ccontext, CoercionForm cformat); -extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, Oid typeId, +extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, + Oid typeId, CoercionForm cformat, bool hideInputCoercion, bool lengthCoercionDone); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 3c48416974..47553c5cf2 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.277 2006/03/10 20:15:27 neilc Exp $ + * $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.278 2006/04/05 22:11:57 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -99,6 +99,10 @@ extern Datum i4tochar(PG_FUNCTION_ARGS); extern Datum text_char(PG_FUNCTION_ARGS); extern Datum char_text(PG_FUNCTION_ARGS); +/* domains.c */ +extern Datum domain_in(PG_FUNCTION_ARGS); +extern Datum domain_recv(PG_FUNCTION_ARGS); + /* int.c */ extern Datum int2in(PG_FUNCTION_ARGS); extern Datum int2out(PG_FUNCTION_ARGS); diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 6e0d0de2d6..9a33fc3602 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.103 2006/03/05 15:59:07 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.104 2006/04/05 22:11:57 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -86,7 +86,6 @@ extern void get_type_io_data(Oid typid, Oid *typioparam, Oid *func); extern char get_typstorage(Oid typid); -extern int32 get_typtypmod(Oid typid); extern Node *get_typdefault(Oid typid); extern char get_typtype(Oid typid); extern Oid get_typ_typrelid(Oid typid); @@ -97,6 +96,7 @@ extern void getTypeOutputInfo(Oid type, Oid *typOutput, bool *typIsVarlena); extern void getTypeBinaryInputInfo(Oid type, Oid *typReceive, Oid *typIOParam); extern void getTypeBinaryOutputInfo(Oid type, Oid *typSend, bool *typIsVarlena); extern Oid getBaseType(Oid typid); +extern Oid getBaseTypeAndTypmod(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 c89be9535f..86e2ca54bf 100644 --- a/src/test/regress/expected/domain.out +++ b/src/test/regress/expected/domain.out @@ -1,10 +1,17 @@ +-- +-- Test domains. +-- -- Test Comment / Drop create domain domaindroptest int4; comment on domain domaindroptest is 'About to drop this..'; --- currently this will be disallowed -create domain basetypetest domaindroptest; -ERROR: "domaindroptest" is not a valid base type for a domain +create domain dependenttypetest domaindroptest; +-- fail because of dependent type drop domain domaindroptest; +NOTICE: type dependenttypetest depends on type domaindroptest +ERROR: cannot drop type domaindroptest because other objects depend on it +HINT: Use DROP ... CASCADE to drop the dependent objects too. +drop domain domaindroptest cascade; +NOTICE: drop cascades to type dependenttypetest -- this should fail because already gone drop domain domaindroptest cascade; ERROR: type "domaindroptest" does not exist @@ -40,7 +47,7 @@ INSERT INTO basictest values ('88', 'haha', 'short', '123.1212'); -- Truncate -- Test copy COPY basictest (testvarchar) FROM stdin; -- fail ERROR: value too long for type character varying(5) -CONTEXT: COPY basictest, line 1: "notsoshorttext" +CONTEXT: COPY basictest, line 1, column testvarchar: "notsoshorttext" COPY basictest (testvarchar) FROM stdin; select * from basictest; testint4 | testtext | testvarchar | testnumeric @@ -126,8 +133,11 @@ ERROR: null value in column "col3" violates not-null constraint INSERT INTO nulltest values ('a', 'b', 'c', NULL, 'd'); -- Good -- Test copy COPY nulltest FROM stdin; --fail +ERROR: null value in column "col3" violates not-null constraint +CONTEXT: COPY nulltest, line 1: "a b \N d d" +COPY nulltest FROM stdin; --fail ERROR: domain dcheck does not allow null values -CONTEXT: COPY nulltest, line 1: "a b \N d \N" +CONTEXT: COPY nulltest, line 1, column col5: NULL input -- Last row is bad COPY nulltest FROM stdin; ERROR: new row for relation "nulltest" violates check constraint "nulltest_col5_check" @@ -300,6 +310,46 @@ drop domain ddef3 restrict; drop domain ddef4 restrict; drop domain ddef5 restrict; drop sequence ddef4_seq; +-- Test domains over domains +create domain vchar4 varchar(4); +create domain dinter vchar4 check (substring(VALUE, 1, 1) = 'x'); +create domain dtop dinter check (substring(VALUE, 2, 1) = '1'); +select 'x123'::dtop; + dtop +------ + x123 +(1 row) + +select 'x1234'::dtop; -- explicit coercion should truncate + dtop +------ + x123 +(1 row) + +select 'y1234'::dtop; -- fail +ERROR: value for domain dtop violates check constraint "dinter_check" +select 'y123'::dtop; -- fail +ERROR: value for domain dtop violates check constraint "dinter_check" +select 'yz23'::dtop; -- fail +ERROR: value for domain dtop violates check constraint "dinter_check" +select 'xz23'::dtop; -- fail +ERROR: value for domain dtop violates check constraint "dtop_check" +create temp table dtest(f1 dtop); +insert into dtest values('x123'); +insert into dtest values('x1234'); -- fail, implicit coercion +ERROR: value too long for type character varying(4) +insert into dtest values('y1234'); -- fail, implicit coercion +ERROR: value too long for type character varying(4) +insert into dtest values('y123'); -- fail +ERROR: value for domain dtop violates check constraint "dinter_check" +insert into dtest values('yz23'); -- fail +ERROR: value for domain dtop violates check constraint "dinter_check" +insert into dtest values('xz23'); -- fail +ERROR: value for domain dtop violates check constraint "dtop_check" +drop table dtest; +drop domain vchar4 cascade; +NOTICE: drop cascades to type dinter +NOTICE: drop cascades to type dtop -- Make sure that constraints of newly-added domain columns are -- enforced correctly, even if there's no default value for the new -- column. Per bug #1433 @@ -328,3 +378,27 @@ execute s1(0); -- should fail ERROR: value for domain pos_int violates check constraint "pos_int_check" execute s1(NULL); -- should fail ERROR: domain pos_int does not allow null values +-- Check that domain constraints on plpgsql function parameters, results, +-- and local variables are enforced correctly. +create function doubledecrement(p1 pos_int) returns pos_int as $$ +declare v pos_int; +begin + v := p1 - 1; + return v - 1; +end$$ language plpgsql; +select doubledecrement(null); -- fail before call +ERROR: domain pos_int does not allow null values +select doubledecrement(0); -- fail before call +ERROR: value for domain pos_int violates check constraint "pos_int_check" +select doubledecrement(1); -- fail at assignment to v +ERROR: value for domain pos_int violates check constraint "pos_int_check" +CONTEXT: PL/pgSQL function "doubledecrement" line 3 at assignment +select doubledecrement(2); -- fail at return +ERROR: value for domain pos_int violates check constraint "pos_int_check" +CONTEXT: PL/pgSQL function "doubledecrement" while casting return value to function's return type +select doubledecrement(3); -- good + doubledecrement +----------------- + 1 +(1 row) + diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql index f6010e636c..21940e0e61 100644 --- a/src/test/regress/sql/domain.sql +++ b/src/test/regress/sql/domain.sql @@ -1,14 +1,18 @@ - +-- +-- Test domains. +-- -- Test Comment / Drop create domain domaindroptest int4; comment on domain domaindroptest is 'About to drop this..'; --- currently this will be disallowed -create domain basetypetest domaindroptest; +create domain dependenttypetest domaindroptest; +-- fail because of dependent type drop domain domaindroptest; +drop domain domaindroptest cascade; + -- this should fail because already gone drop domain domaindroptest cascade; @@ -101,7 +105,11 @@ INSERT INTO nulltest values ('a', 'b', 'c', NULL, 'd'); -- Good -- Test copy COPY nulltest FROM stdin; --fail -a b \N d \N +a b \N d d +\. + +COPY nulltest FROM stdin; --fail +a b c d \N \. -- Last row is bad @@ -245,6 +253,30 @@ drop domain ddef4 restrict; drop domain ddef5 restrict; drop sequence ddef4_seq; +-- Test domains over domains +create domain vchar4 varchar(4); +create domain dinter vchar4 check (substring(VALUE, 1, 1) = 'x'); +create domain dtop dinter check (substring(VALUE, 2, 1) = '1'); + +select 'x123'::dtop; +select 'x1234'::dtop; -- explicit coercion should truncate +select 'y1234'::dtop; -- fail +select 'y123'::dtop; -- fail +select 'yz23'::dtop; -- fail +select 'xz23'::dtop; -- fail + +create temp table dtest(f1 dtop); + +insert into dtest values('x123'); +insert into dtest values('x1234'); -- fail, implicit coercion +insert into dtest values('y1234'); -- fail, implicit coercion +insert into dtest values('y123'); -- fail +insert into dtest values('yz23'); -- fail +insert into dtest values('xz23'); -- fail + +drop table dtest; +drop domain vchar4 cascade; + -- Make sure that constraints of newly-added domain columns are -- enforced correctly, even if there's no default value for the new -- column. Per bug #1433 @@ -271,3 +303,19 @@ prepare s1 as select $1::pos_int = 10 as "is_ten"; execute s1(10); execute s1(0); -- should fail execute s1(NULL); -- should fail + +-- Check that domain constraints on plpgsql function parameters, results, +-- and local variables are enforced correctly. + +create function doubledecrement(p1 pos_int) returns pos_int as $$ +declare v pos_int; +begin + v := p1 - 1; + return v - 1; +end$$ language plpgsql; + +select doubledecrement(null); -- fail before call +select doubledecrement(0); -- fail before call +select doubledecrement(1); -- fail at assignment to v +select doubledecrement(2); -- fail at return +select doubledecrement(3); -- good