LCOV - code coverage report
Current view: top level - src/backend/utils/adt - domains.c (source / functions) Hit Total Coverage
Test: PostgreSQL 12beta2 Lines: 79 100 79.0 %
Date: 2019-06-19 16:07:09 Functions: 6 7 85.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * domains.c
       4             :  *    I/O functions for domain types.
       5             :  *
       6             :  * The output functions for a domain type are just the same ones provided
       7             :  * by its underlying base type.  The input functions, however, must be
       8             :  * prepared to apply any constraints defined by the type.  So, we create
       9             :  * special input functions that invoke the base type's input function
      10             :  * and then check the constraints.
      11             :  *
      12             :  * The overhead required for constraint checking can be high, since examining
      13             :  * the catalogs to discover the constraints for a given domain is not cheap.
      14             :  * We have three mechanisms for minimizing this cost:
      15             :  *  1.  We rely on the typcache to keep up-to-date copies of the constraints.
      16             :  *  2.  In a nest of domains, we flatten the checking of all the levels
      17             :  *      into just one operation (the typcache does this for us).
      18             :  *  3.  If there are CHECK constraints, we cache a standalone ExprContext
      19             :  *      to evaluate them in.
      20             :  *
      21             :  *
      22             :  * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
      23             :  * Portions Copyright (c) 1994, Regents of the University of California
      24             :  *
      25             :  *
      26             :  * IDENTIFICATION
      27             :  *    src/backend/utils/adt/domains.c
      28             :  *
      29             :  *-------------------------------------------------------------------------
      30             :  */
      31             : #include "postgres.h"
      32             : 
      33             : #include "access/htup_details.h"
      34             : #include "catalog/pg_type.h"
      35             : #include "executor/executor.h"
      36             : #include "lib/stringinfo.h"
      37             : #include "utils/builtins.h"
      38             : #include "utils/expandeddatum.h"
      39             : #include "utils/lsyscache.h"
      40             : #include "utils/syscache.h"
      41             : #include "utils/typcache.h"
      42             : 
      43             : 
      44             : /*
      45             :  * structure to cache state across multiple calls
      46             :  */
      47             : typedef struct DomainIOData
      48             : {
      49             :     Oid         domain_type;
      50             :     /* Data needed to call base type's input function */
      51             :     Oid         typiofunc;
      52             :     Oid         typioparam;
      53             :     int32       typtypmod;
      54             :     FmgrInfo    proc;
      55             :     /* Reference to cached list of constraint items to check */
      56             :     DomainConstraintRef constraint_ref;
      57             :     /* Context for evaluating CHECK constraints in */
      58             :     ExprContext *econtext;
      59             :     /* Memory context this cache is in */
      60             :     MemoryContext mcxt;
      61             : } DomainIOData;
      62             : 
      63             : 
      64             : /*
      65             :  * domain_state_setup - initialize the cache for a new domain type.
      66             :  *
      67             :  * Note: we can't re-use the same cache struct for a new domain type,
      68             :  * since there's no provision for releasing the DomainConstraintRef.
      69             :  * If a call site needs to deal with a new domain type, we just leak
      70             :  * the old struct for the duration of the query.
      71             :  */
      72             : static DomainIOData *
      73        3584 : domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt)
      74             : {
      75             :     DomainIOData *my_extra;
      76             :     TypeCacheEntry *typentry;
      77             :     Oid         baseType;
      78             : 
      79        3584 :     my_extra = (DomainIOData *) MemoryContextAlloc(mcxt, sizeof(DomainIOData));
      80             : 
      81             :     /*
      82             :      * Verify that domainType represents a valid domain type.  We need to be
      83             :      * careful here because domain_in and domain_recv can be called from SQL,
      84             :      * possibly with incorrect arguments.  We use lookup_type_cache mainly
      85             :      * because it will throw a clean user-facing error for a bad OID; but also
      86             :      * it can cache the underlying base type info.
      87             :      */
      88        3584 :     typentry = lookup_type_cache(domainType, TYPECACHE_DOMAIN_BASE_INFO);
      89        3584 :     if (typentry->typtype != TYPTYPE_DOMAIN)
      90           0 :         ereport(ERROR,
      91             :                 (errcode(ERRCODE_DATATYPE_MISMATCH),
      92             :                  errmsg("type %s is not a domain",
      93             :                         format_type_be(domainType))));
      94             : 
      95             :     /* Find out the base type */
      96        3584 :     baseType = typentry->domainBaseType;
      97        3584 :     my_extra->typtypmod = typentry->domainBaseTypmod;
      98             : 
      99             :     /* Look up underlying I/O function */
     100        3584 :     if (binary)
     101        1434 :         getTypeBinaryInputInfo(baseType,
     102             :                                &my_extra->typiofunc,
     103             :                                &my_extra->typioparam);
     104             :     else
     105        2150 :         getTypeInputInfo(baseType,
     106             :                          &my_extra->typiofunc,
     107             :                          &my_extra->typioparam);
     108        3584 :     fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, mcxt);
     109             : 
     110             :     /* Look up constraints for domain */
     111        3584 :     InitDomainConstraintRef(domainType, &my_extra->constraint_ref, mcxt, true);
     112             : 
     113             :     /* We don't make an ExprContext until needed */
     114        3584 :     my_extra->econtext = NULL;
     115        3584 :     my_extra->mcxt = mcxt;
     116             : 
     117             :     /* Mark cache valid */
     118        3584 :     my_extra->domain_type = domainType;
     119             : 
     120        3584 :     return my_extra;
     121             : }
     122             : 
     123             : /*
     124             :  * domain_check_input - apply the cached checks.
     125             :  *
     126             :  * This is roughly similar to the handling of CoerceToDomain nodes in
     127             :  * execExpr*.c, but we execute each constraint separately, rather than
     128             :  * compiling them in-line within a larger expression.
     129             :  */
     130             : static void
     131     1368472 : domain_check_input(Datum value, bool isnull, DomainIOData *my_extra)
     132             : {
     133     1368472 :     ExprContext *econtext = my_extra->econtext;
     134             :     ListCell   *l;
     135             : 
     136             :     /* Make sure we have up-to-date constraints */
     137     1368472 :     UpdateDomainConstraintRef(&my_extra->constraint_ref);
     138             : 
     139     1598230 :     foreach(l, my_extra->constraint_ref.constraints)
     140             :     {
     141      229916 :         DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
     142             : 
     143      229916 :         switch (con->constrainttype)
     144             :         {
     145             :             case DOM_CONSTRAINT_NOTNULL:
     146         130 :                 if (isnull)
     147          50 :                     ereport(ERROR,
     148             :                             (errcode(ERRCODE_NOT_NULL_VIOLATION),
     149             :                              errmsg("domain %s does not allow null values",
     150             :                                     format_type_be(my_extra->domain_type)),
     151             :                              errdatatype(my_extra->domain_type)));
     152          80 :                 break;
     153             :             case DOM_CONSTRAINT_CHECK:
     154             :                 {
     155             :                     /* Make the econtext if we didn't already */
     156      229786 :                     if (econtext == NULL)
     157             :                     {
     158             :                         MemoryContext oldcontext;
     159             : 
     160        1788 :                         oldcontext = MemoryContextSwitchTo(my_extra->mcxt);
     161        1788 :                         econtext = CreateStandaloneExprContext();
     162        1788 :                         MemoryContextSwitchTo(oldcontext);
     163        1788 :                         my_extra->econtext = econtext;
     164             :                     }
     165             : 
     166             :                     /*
     167             :                      * Set up value to be returned by CoerceToDomainValue
     168             :                      * nodes.  Unlike in the generic expression case, this
     169             :                      * econtext couldn't be shared with anything else, so no
     170             :                      * need to save and restore fields.  But we do need to
     171             :                      * protect the passed-in value against being changed by
     172             :                      * called functions.  (It couldn't be a R/W expanded
     173             :                      * object for most uses, but that seems possible for
     174             :                      * domain_check().)
     175             :                      */
     176      229786 :                     econtext->domainValue_datum =
     177      229786 :                         MakeExpandedObjectReadOnly(value, isnull,
     178             :                                                    my_extra->constraint_ref.tcache->typlen);
     179      229786 :                     econtext->domainValue_isNull = isnull;
     180             : 
     181      229786 :                     if (!ExecCheck(con->check_exprstate, econtext))
     182         108 :                         ereport(ERROR,
     183             :                                 (errcode(ERRCODE_CHECK_VIOLATION),
     184             :                                  errmsg("value for domain %s violates check constraint \"%s\"",
     185             :                                         format_type_be(my_extra->domain_type),
     186             :                                         con->name),
     187             :                                  errdomainconstraint(my_extra->domain_type,
     188             :                                                      con->name)));
     189      229678 :                     break;
     190             :                 }
     191             :             default:
     192           0 :                 elog(ERROR, "unrecognized constraint type: %d",
     193             :                      (int) con->constrainttype);
     194             :                 break;
     195             :         }
     196             :     }
     197             : 
     198             :     /*
     199             :      * Before exiting, call any shutdown callbacks and reset econtext's
     200             :      * per-tuple memory.  This avoids leaking non-memory resources, if
     201             :      * anything in the expression(s) has any.
     202             :      */
     203     1368314 :     if (econtext)
     204      229678 :         ReScanExprContext(econtext);
     205     1368314 : }
     206             : 
     207             : 
     208             : /*
     209             :  * domain_in        - input routine for any domain type.
     210             :  */
     211             : Datum
     212     1366430 : domain_in(PG_FUNCTION_ARGS)
     213             : {
     214             :     char       *string;
     215             :     Oid         domainType;
     216             :     DomainIOData *my_extra;
     217             :     Datum       value;
     218             : 
     219             :     /*
     220             :      * Since domain_in is not strict, we have to check for null inputs. The
     221             :      * typioparam argument should never be null in normal system usage, but it
     222             :      * could be null in a manual invocation --- if so, just return null.
     223             :      */
     224     1366430 :     if (PG_ARGISNULL(0))
     225          78 :         string = NULL;
     226             :     else
     227     1366352 :         string = PG_GETARG_CSTRING(0);
     228     1366430 :     if (PG_ARGISNULL(1))
     229           0 :         PG_RETURN_NULL();
     230     1366430 :     domainType = PG_GETARG_OID(1);
     231             : 
     232             :     /*
     233             :      * We arrange to look up the needed info just once per series of calls,
     234             :      * assuming the domain type doesn't change underneath us (which really
     235             :      * shouldn't happen, but cope if it does).
     236             :      */
     237     1366430 :     my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra;
     238     1366430 :     if (my_extra == NULL || my_extra->domain_type != domainType)
     239             :     {
     240        2150 :         my_extra = domain_state_setup(domainType, false,
     241        2150 :                                       fcinfo->flinfo->fn_mcxt);
     242        2150 :         fcinfo->flinfo->fn_extra = (void *) my_extra;
     243             :     }
     244             : 
     245             :     /*
     246             :      * Invoke the base type's typinput procedure to convert the data.
     247             :      */
     248     1366430 :     value = InputFunctionCall(&my_extra->proc,
     249             :                               string,
     250             :                               my_extra->typioparam,
     251             :                               my_extra->typtypmod);
     252             : 
     253             :     /*
     254             :      * Do the necessary checks to ensure it's a valid domain value.
     255             :      */
     256     1366422 :     domain_check_input(value, (string == NULL), my_extra);
     257             : 
     258     1366384 :     if (string == NULL)
     259          92 :         PG_RETURN_NULL();
     260             :     else
     261     1366292 :         PG_RETURN_DATUM(value);
     262             : }
     263             : 
     264             : /*
     265             :  * domain_recv      - binary input routine for any domain type.
     266             :  */
     267             : Datum
     268           0 : domain_recv(PG_FUNCTION_ARGS)
     269             : {
     270             :     StringInfo  buf;
     271             :     Oid         domainType;
     272             :     DomainIOData *my_extra;
     273             :     Datum       value;
     274             : 
     275             :     /*
     276             :      * Since domain_recv is not strict, we have to check for null inputs. The
     277             :      * typioparam argument should never be null in normal system usage, but it
     278             :      * could be null in a manual invocation --- if so, just return null.
     279             :      */
     280           0 :     if (PG_ARGISNULL(0))
     281           0 :         buf = NULL;
     282             :     else
     283           0 :         buf = (StringInfo) PG_GETARG_POINTER(0);
     284           0 :     if (PG_ARGISNULL(1))
     285           0 :         PG_RETURN_NULL();
     286           0 :     domainType = PG_GETARG_OID(1);
     287             : 
     288             :     /*
     289             :      * We arrange to look up the needed info just once per series of calls,
     290             :      * assuming the domain type doesn't change underneath us (which really
     291             :      * shouldn't happen, but cope if it does).
     292             :      */
     293           0 :     my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra;
     294           0 :     if (my_extra == NULL || my_extra->domain_type != domainType)
     295             :     {
     296           0 :         my_extra = domain_state_setup(domainType, true,
     297           0 :                                       fcinfo->flinfo->fn_mcxt);
     298           0 :         fcinfo->flinfo->fn_extra = (void *) my_extra;
     299             :     }
     300             : 
     301             :     /*
     302             :      * Invoke the base type's typreceive procedure to convert the data.
     303             :      */
     304           0 :     value = ReceiveFunctionCall(&my_extra->proc,
     305             :                                 buf,
     306             :                                 my_extra->typioparam,
     307             :                                 my_extra->typtypmod);
     308             : 
     309             :     /*
     310             :      * Do the necessary checks to ensure it's a valid domain value.
     311             :      */
     312           0 :     domain_check_input(value, (buf == NULL), my_extra);
     313             : 
     314           0 :     if (buf == NULL)
     315           0 :         PG_RETURN_NULL();
     316             :     else
     317           0 :         PG_RETURN_DATUM(value);
     318             : }
     319             : 
     320             : /*
     321             :  * domain_check - check that a datum satisfies the constraints of a
     322             :  * domain.  extra and mcxt can be passed if they are available from,
     323             :  * say, a FmgrInfo structure, or they can be NULL, in which case the
     324             :  * setup is repeated for each call.
     325             :  */
     326             : void
     327        2050 : domain_check(Datum value, bool isnull, Oid domainType,
     328             :              void **extra, MemoryContext mcxt)
     329             : {
     330        2050 :     DomainIOData *my_extra = NULL;
     331             : 
     332        2050 :     if (mcxt == NULL)
     333          24 :         mcxt = CurrentMemoryContext;
     334             : 
     335             :     /*
     336             :      * We arrange to look up the needed info just once per series of calls,
     337             :      * assuming the domain type doesn't change underneath us (which really
     338             :      * shouldn't happen, but cope if it does).
     339             :      */
     340        2050 :     if (extra)
     341        2026 :         my_extra = (DomainIOData *) *extra;
     342        2050 :     if (my_extra == NULL || my_extra->domain_type != domainType)
     343             :     {
     344        1434 :         my_extra = domain_state_setup(domainType, true, mcxt);
     345        1434 :         if (extra)
     346        1410 :             *extra = (void *) my_extra;
     347             :     }
     348             : 
     349             :     /*
     350             :      * Do the necessary checks to ensure it's a valid domain value.
     351             :      */
     352        2050 :     domain_check_input(value, isnull, my_extra);
     353        1930 : }
     354             : 
     355             : /*
     356             :  * errdatatype --- stores schema_name and datatype_name of a datatype
     357             :  * within the current errordata.
     358             :  */
     359             : int
     360         458 : errdatatype(Oid datatypeOid)
     361             : {
     362             :     HeapTuple   tup;
     363             :     Form_pg_type typtup;
     364             : 
     365         458 :     tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(datatypeOid));
     366         458 :     if (!HeapTupleIsValid(tup))
     367           0 :         elog(ERROR, "cache lookup failed for type %u", datatypeOid);
     368         458 :     typtup = (Form_pg_type) GETSTRUCT(tup);
     369             : 
     370         458 :     err_generic_string(PG_DIAG_SCHEMA_NAME,
     371         458 :                        get_namespace_name(typtup->typnamespace));
     372         458 :     err_generic_string(PG_DIAG_DATATYPE_NAME, NameStr(typtup->typname));
     373             : 
     374         458 :     ReleaseSysCache(tup);
     375             : 
     376         458 :     return 0;                   /* return value does not matter */
     377             : }
     378             : 
     379             : /*
     380             :  * errdomainconstraint --- stores schema_name, datatype_name and
     381             :  * constraint_name of a domain-related constraint within the current errordata.
     382             :  */
     383             : int
     384         344 : errdomainconstraint(Oid datatypeOid, const char *conname)
     385             : {
     386         344 :     errdatatype(datatypeOid);
     387         344 :     err_generic_string(PG_DIAG_CONSTRAINT_NAME, conname);
     388             : 
     389         344 :     return 0;                   /* return value does not matter */
     390             : }

Generated by: LCOV version 1.13