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.
This commit is contained in:
Tom Lane 2006-04-05 22:11:58 +00:00
parent 89a67e523e
commit 7fdb4305db
20 changed files with 549 additions and 196 deletions

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/ref/create_domain.sgml,v 1.27 2005/12/25 01:41:15 neilc Exp $
$PostgreSQL: pgsql/doc/src/sgml/ref/create_domain.sgml,v 1.28 2006/04/05 22:11:54 tgl Exp $
PostgreSQL documentation
-->
@ -35,8 +35,10 @@ where <replaceable class="PARAMETER">constraint</replaceable> is:
<title>Description</title>
<para>
<command>CREATE DOMAIN</command> creates a new data domain. The
user who defines a domain becomes its owner.
<command>CREATE DOMAIN</command> 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.
</para>
<para>
@ -48,24 +50,13 @@ where <replaceable class="PARAMETER">constraint</replaceable> is:
</para>
<para>
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.
</para>
<caution>
<para>
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 <application>PL/pgSQL</>, one possible
workaround is to explicitly cast the result value to the domain type
when you return it. <application>PL/pgSQL</> does not enforce domain
constraints for local variables within functions, either.
</para>
</caution>
</refsect1>
<refsect1>
@ -156,7 +147,7 @@ where <replaceable class="PARAMETER">constraint</replaceable> is:
<literal>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 <literal>VALUE</>
producing a Boolean result. It should use the key word <literal>VALUE</>
to refer to the value being tested.
</para>
@ -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
);
</programlisting>
</para>

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/ref/create_type.sgml,v 1.62 2006/04/04 19:35:32 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/ref/create_type.sgml,v 1.63 2006/04/05 22:11:54 tgl Exp $
PostgreSQL documentation
-->
@ -591,6 +591,7 @@ CREATE TABLE big_objs (
<member><xref linkend="sql-createfunction" endterm="sql-createfunction-title"></member>
<member><xref linkend="sql-droptype" endterm="sql-droptype-title"></member>
<member><xref linkend="sql-altertype" endterm="sql-altertype-title"></member>
<member><xref linkend="sql-createdomain" endterm="sql-createdomain-title"></member>
</simplelist>
</refsect1>

View File

@ -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 */

View File

@ -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);

View File

@ -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) */

View File

@ -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,

View File

@ -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);

View File

@ -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,

View File

@ -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,

View File

@ -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 \

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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

View File

@ -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_ ));

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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,

View File

@ -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)

View File

@ -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