Fix a bunch of problems with domains by making them use special input functions
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 5 Apr 2006 22:11:58 +0000 (22:11 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 5 Apr 2006 22:11:58 +0000 (22:11 +0000)
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.

20 files changed:
doc/src/sgml/ref/create_domain.sgml
doc/src/sgml/ref/create_type.sgml
src/backend/access/common/printtup.c
src/backend/commands/copy.c
src/backend/commands/typecmds.c
src/backend/optimizer/prep/preptlist.c
src/backend/parser/parse_coerce.c
src/backend/rewrite/rewriteHandler.c
src/backend/rewrite/rewriteManip.c
src/backend/utils/adt/Makefile
src/backend/utils/adt/domains.c [new file with mode: 0644]
src/backend/utils/cache/lsyscache.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.h
src/include/catalog/pg_type.h
src/include/parser/parse_coerce.h
src/include/utils/builtins.h
src/include/utils/lsyscache.h
src/test/regress/expected/domain.out
src/test/regress/sql/domain.sql

index 4beb0ae53b8393ce7066f37ea687faa7aa0edd84..bfed9027399ea1c99b2fbc6a855adf7db02c219e 100644 (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>
index e3b8b44d8f9ddf5a784fd7815aaff5b0fc7fabab..68ec242ae5a2cb69d31df61a397a7c1e1088ca90 100644 (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>
 
index ba5793b0e7eb3581c39032b5defb8ece5081bb0a..7eb46bb6ec54132428a8bc4059edf4eaf4e33911 100644 (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 */
index af21d565f1781bbc392c9234324ca58ad6588a30..2b49094bea014f375df8fcf0cf6bb68c6ed065a9 100644 (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);
index 83143496dbccbc0927595006be1e11dbb778d08f..21546d1c8b08c02eb5192914471591aef4d991de 100644 (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) */
index 436d6fdce0651dab28caffa6a5c1e55e862e22b4..5a37b86eb5bcb6f6f54c3edc6bc100411f1da1b2 100644 (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,
index 5a343e768dda1a9492fb68f45a4c259e1ae88925..98393a54aa11d64564bbc0822a214c35d8e470a2 100644 (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);
index acebefca3f0f278bed804dd4fe61a1746201b0d2..6d1ace66f1afe07b666f24c5dee3c886e742882d 100644 (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,
index d7cd8de6774e5d19e920742a42db134d8e0ae1ad..175df7a695a1ffda9e048410a655b016b760a4d9 100644 (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,
index 294fd9de49b757e745bfb4343bc14982b0d931ab..5a1996c3439461cece6b6f06df8c21c85a5c9c38 100644 (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 \
diff --git a/src/backend/utils/adt/domains.c b/src/backend/utils/adt/domains.c
new file mode 100644 (file)
index 0000000..051145f
--- /dev/null
@@ -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);
+}
index fb24cc623660bcb7fd162af4262b252f35abcc24..a52366c5347b5334863ccee35d5f1cd44c6c091a 100644 (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);
    }
 
index 2ae52c723fce01494ecff641dde23f09b03c33f2..6b59dd55fe1d3753d816625b486a9919d4106cd2 100644 (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
index d8fda0b8c3aa4ea33de52264bc96eb9aaa1724c4..9542d632f2518ea9c6c5db4b483cb8a8c92fe8e5 100644 (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_ ));
index 00b7bf3dc3c576a659c09c212fcc05011cd3c57d..b9aeec1e3cbe7a9753a074979fb70121c8f38060 100644 (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;
index 871c82892d58857db0fb0119326dd33835c2a28a..70b7d0748977225140058253649f1ac5847db592 100644 (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);
 
index 3c4841697409729ef2fd640c3123e02091d2d2f4..47553c5cf291dcc4c169b48c4d37982267dd0a0b 100644 (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);
index 6e0d0de2d6fa125d60bbe41067ac5c0baaa41498..9a33fc360212af964b5642a2e1e82d0f34064a30 100644 (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,
index c89be9535f17721efaff82c94192b5db749ef893..86e2ca54bf9297031886fc0f121a2f5c33ea1990 100644 (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)
+
index f6010e636cb8dcdd6a9fc3be92bca4c38e105a70..21940e0e618cc8cb882dd212a3ac3e81084a4fd4 100644 (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