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