LCOV - code coverage report
Current view: top level - src/backend/utils/adt - domains.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 86 108 79.6 %
Date: 2023-12-05 08:11:01 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-2023, 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        2800 : domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt)
      74             : {
      75             :     DomainIOData *my_extra;
      76             :     TypeCacheEntry *typentry;
      77             :     Oid         baseType;
      78             : 
      79        2800 :     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        2800 :     typentry = lookup_type_cache(domainType, TYPECACHE_DOMAIN_BASE_INFO);
      89        2800 :     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        2800 :     baseType = typentry->domainBaseType;
      97        2800 :     my_extra->typtypmod = typentry->domainBaseTypmod;
      98             : 
      99             :     /* Look up underlying I/O function */
     100        2800 :     if (binary)
     101        2078 :         getTypeBinaryInputInfo(baseType,
     102             :                                &my_extra->typiofunc,
     103             :                                &my_extra->typioparam);
     104             :     else
     105         722 :         getTypeInputInfo(baseType,
     106             :                          &my_extra->typiofunc,
     107             :                          &my_extra->typioparam);
     108        2800 :     fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, mcxt);
     109             : 
     110             :     /* Look up constraints for domain */
     111        2800 :     InitDomainConstraintRef(domainType, &my_extra->constraint_ref, mcxt, true);
     112             : 
     113             :     /* We don't make an ExprContext until needed */
     114        2800 :     my_extra->econtext = NULL;
     115        2800 :     my_extra->mcxt = mcxt;
     116             : 
     117             :     /* Mark cache valid */
     118        2800 :     my_extra->domain_type = domainType;
     119             : 
     120        2800 :     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             :  * If escontext points to an ErrorSaveContext, any failures are reported
     131             :  * there, otherwise they are ereport'ed.  Note that we do not attempt to do
     132             :  * soft reporting of errors raised during execution of CHECK constraints.
     133             :  */
     134             : static void
     135      275552 : domain_check_input(Datum value, bool isnull, DomainIOData *my_extra,
     136             :                    Node *escontext)
     137             : {
     138      275552 :     ExprContext *econtext = my_extra->econtext;
     139             :     ListCell   *l;
     140             : 
     141             :     /* Make sure we have up-to-date constraints */
     142      275552 :     UpdateDomainConstraintRef(&my_extra->constraint_ref);
     143             : 
     144      323920 :     foreach(l, my_extra->constraint_ref.constraints)
     145             :     {
     146       48566 :         DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
     147             : 
     148       48566 :         switch (con->constrainttype)
     149             :         {
     150         168 :             case DOM_CONSTRAINT_NOTNULL:
     151         168 :                 if (isnull)
     152             :                 {
     153          56 :                     errsave(escontext,
     154             :                             (errcode(ERRCODE_NOT_NULL_VIOLATION),
     155             :                              errmsg("domain %s does not allow null values",
     156             :                                     format_type_be(my_extra->domain_type)),
     157             :                              errdatatype(my_extra->domain_type)));
     158          18 :                     goto fail;
     159             :                 }
     160         112 :                 break;
     161       48398 :             case DOM_CONSTRAINT_CHECK:
     162             :                 {
     163             :                     /* Make the econtext if we didn't already */
     164       48398 :                     if (econtext == NULL)
     165             :                     {
     166             :                         MemoryContext oldcontext;
     167             : 
     168        2194 :                         oldcontext = MemoryContextSwitchTo(my_extra->mcxt);
     169        2194 :                         econtext = CreateStandaloneExprContext();
     170        2194 :                         MemoryContextSwitchTo(oldcontext);
     171        2194 :                         my_extra->econtext = econtext;
     172             :                     }
     173             : 
     174             :                     /*
     175             :                      * Set up value to be returned by CoerceToDomainValue
     176             :                      * nodes.  Unlike in the generic expression case, this
     177             :                      * econtext couldn't be shared with anything else, so no
     178             :                      * need to save and restore fields.  But we do need to
     179             :                      * protect the passed-in value against being changed by
     180             :                      * called functions.  (It couldn't be a R/W expanded
     181             :                      * object for most uses, but that seems possible for
     182             :                      * domain_check().)
     183             :                      */
     184       48398 :                     econtext->domainValue_datum =
     185       48398 :                         MakeExpandedObjectReadOnly(value, isnull,
     186             :                                                    my_extra->constraint_ref.tcache->typlen);
     187       48398 :                     econtext->domainValue_isNull = isnull;
     188             : 
     189       48398 :                     if (!ExecCheck(con->check_exprstate, econtext))
     190             :                     {
     191         136 :                         errsave(escontext,
     192             :                                 (errcode(ERRCODE_CHECK_VIOLATION),
     193             :                                  errmsg("value for domain %s violates check constraint \"%s\"",
     194             :                                         format_type_be(my_extra->domain_type),
     195             :                                         con->name),
     196             :                                  errdomainconstraint(my_extra->domain_type,
     197             :                                                      con->name)));
     198          18 :                         goto fail;
     199             :                     }
     200       48256 :                     break;
     201             :                 }
     202           0 :             default:
     203           0 :                 elog(ERROR, "unrecognized constraint type: %d",
     204             :                      (int) con->constrainttype);
     205             :                 break;
     206             :         }
     207             :     }
     208             : 
     209             :     /*
     210             :      * Before exiting, call any shutdown callbacks and reset econtext's
     211             :      * per-tuple memory.  This avoids leaking non-memory resources, if
     212             :      * anything in the expression(s) has any.
     213             :      */
     214      275372 : fail:
     215      275372 :     if (econtext)
     216       48274 :         ReScanExprContext(econtext);
     217      275372 : }
     218             : 
     219             : 
     220             : /*
     221             :  * domain_in        - input routine for any domain type.
     222             :  */
     223             : Datum
     224      272612 : domain_in(PG_FUNCTION_ARGS)
     225             : {
     226             :     char       *string;
     227             :     Oid         domainType;
     228      272612 :     Node       *escontext = fcinfo->context;
     229             :     DomainIOData *my_extra;
     230             :     Datum       value;
     231             : 
     232             :     /*
     233             :      * Since domain_in is not strict, we have to check for null inputs. The
     234             :      * typioparam argument should never be null in normal system usage, but it
     235             :      * could be null in a manual invocation --- if so, just return null.
     236             :      */
     237      272612 :     if (PG_ARGISNULL(0))
     238         110 :         string = NULL;
     239             :     else
     240      272502 :         string = PG_GETARG_CSTRING(0);
     241      272612 :     if (PG_ARGISNULL(1))
     242           0 :         PG_RETURN_NULL();
     243      272612 :     domainType = PG_GETARG_OID(1);
     244             : 
     245             :     /*
     246             :      * We arrange to look up the needed info just once per series of calls,
     247             :      * assuming the domain type doesn't change underneath us (which really
     248             :      * shouldn't happen, but cope if it does).
     249             :      */
     250      272612 :     my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra;
     251      272612 :     if (my_extra == NULL || my_extra->domain_type != domainType)
     252             :     {
     253         722 :         my_extra = domain_state_setup(domainType, false,
     254         722 :                                       fcinfo->flinfo->fn_mcxt);
     255         722 :         fcinfo->flinfo->fn_extra = (void *) my_extra;
     256             :     }
     257             : 
     258             :     /*
     259             :      * Invoke the base type's typinput procedure to convert the data.
     260             :      */
     261      272612 :     if (!InputFunctionCallSafe(&my_extra->proc,
     262             :                                string,
     263             :                                my_extra->typioparam,
     264             :                                my_extra->typtypmod,
     265             :                                escontext,
     266             :                                &value))
     267          18 :         PG_RETURN_NULL();
     268             : 
     269             :     /*
     270             :      * Do the necessary checks to ensure it's a valid domain value.
     271             :      */
     272      272582 :     domain_check_input(value, (string == NULL), my_extra, escontext);
     273             : 
     274      272542 :     if (string == NULL)
     275         138 :         PG_RETURN_NULL();
     276             :     else
     277      272404 :         PG_RETURN_DATUM(value);
     278             : }
     279             : 
     280             : /*
     281             :  * domain_recv      - binary input routine for any domain type.
     282             :  */
     283             : Datum
     284           0 : domain_recv(PG_FUNCTION_ARGS)
     285             : {
     286             :     StringInfo  buf;
     287             :     Oid         domainType;
     288             :     DomainIOData *my_extra;
     289             :     Datum       value;
     290             : 
     291             :     /*
     292             :      * Since domain_recv is not strict, we have to check for null inputs. The
     293             :      * typioparam argument should never be null in normal system usage, but it
     294             :      * could be null in a manual invocation --- if so, just return null.
     295             :      */
     296           0 :     if (PG_ARGISNULL(0))
     297           0 :         buf = NULL;
     298             :     else
     299           0 :         buf = (StringInfo) PG_GETARG_POINTER(0);
     300           0 :     if (PG_ARGISNULL(1))
     301           0 :         PG_RETURN_NULL();
     302           0 :     domainType = PG_GETARG_OID(1);
     303             : 
     304             :     /*
     305             :      * We arrange to look up the needed info just once per series of calls,
     306             :      * assuming the domain type doesn't change underneath us (which really
     307             :      * shouldn't happen, but cope if it does).
     308             :      */
     309           0 :     my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra;
     310           0 :     if (my_extra == NULL || my_extra->domain_type != domainType)
     311             :     {
     312           0 :         my_extra = domain_state_setup(domainType, true,
     313           0 :                                       fcinfo->flinfo->fn_mcxt);
     314           0 :         fcinfo->flinfo->fn_extra = (void *) my_extra;
     315             :     }
     316             : 
     317             :     /*
     318             :      * Invoke the base type's typreceive procedure to convert the data.
     319             :      */
     320           0 :     value = ReceiveFunctionCall(&my_extra->proc,
     321             :                                 buf,
     322             :                                 my_extra->typioparam,
     323             :                                 my_extra->typtypmod);
     324             : 
     325             :     /*
     326             :      * Do the necessary checks to ensure it's a valid domain value.
     327             :      */
     328           0 :     domain_check_input(value, (buf == NULL), my_extra, NULL);
     329             : 
     330           0 :     if (buf == NULL)
     331           0 :         PG_RETURN_NULL();
     332             :     else
     333           0 :         PG_RETURN_DATUM(value);
     334             : }
     335             : 
     336             : /*
     337             :  * domain_check - check that a datum satisfies the constraints of a
     338             :  * domain.  extra and mcxt can be passed if they are available from,
     339             :  * say, a FmgrInfo structure, or they can be NULL, in which case the
     340             :  * setup is repeated for each call.
     341             :  */
     342             : void
     343        2970 : domain_check(Datum value, bool isnull, Oid domainType,
     344             :              void **extra, MemoryContext mcxt)
     345             : {
     346        2970 :     DomainIOData *my_extra = NULL;
     347             : 
     348        2970 :     if (mcxt == NULL)
     349          24 :         mcxt = CurrentMemoryContext;
     350             : 
     351             :     /*
     352             :      * We arrange to look up the needed info just once per series of calls,
     353             :      * assuming the domain type doesn't change underneath us (which really
     354             :      * shouldn't happen, but cope if it does).
     355             :      */
     356        2970 :     if (extra)
     357        2946 :         my_extra = (DomainIOData *) *extra;
     358        2970 :     if (my_extra == NULL || my_extra->domain_type != domainType)
     359             :     {
     360        2078 :         my_extra = domain_state_setup(domainType, true, mcxt);
     361        2078 :         if (extra)
     362        2054 :             *extra = (void *) my_extra;
     363             :     }
     364             : 
     365             :     /*
     366             :      * Do the necessary checks to ensure it's a valid domain value.
     367             :      */
     368        2970 :     domain_check_input(value, isnull, my_extra, NULL);
     369        2830 : }
     370             : 
     371             : /*
     372             :  * errdatatype --- stores schema_name and datatype_name of a datatype
     373             :  * within the current errordata.
     374             :  */
     375             : int
     376         648 : errdatatype(Oid datatypeOid)
     377             : {
     378             :     HeapTuple   tup;
     379             :     Form_pg_type typtup;
     380             : 
     381         648 :     tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(datatypeOid));
     382         648 :     if (!HeapTupleIsValid(tup))
     383           0 :         elog(ERROR, "cache lookup failed for type %u", datatypeOid);
     384         648 :     typtup = (Form_pg_type) GETSTRUCT(tup);
     385             : 
     386         648 :     err_generic_string(PG_DIAG_SCHEMA_NAME,
     387         648 :                        get_namespace_name(typtup->typnamespace));
     388         648 :     err_generic_string(PG_DIAG_DATATYPE_NAME, NameStr(typtup->typname));
     389             : 
     390         648 :     ReleaseSysCache(tup);
     391             : 
     392         648 :     return 0;                   /* return value does not matter */
     393             : }
     394             : 
     395             : /*
     396             :  * errdomainconstraint --- stores schema_name, datatype_name and
     397             :  * constraint_name of a domain-related constraint within the current errordata.
     398             :  */
     399             : int
     400         498 : errdomainconstraint(Oid datatypeOid, const char *conname)
     401             : {
     402         498 :     errdatatype(datatypeOid);
     403         498 :     err_generic_string(PG_DIAG_CONSTRAINT_NAME, conname);
     404             : 
     405         498 :     return 0;                   /* return value does not matter */
     406             : }

Generated by: LCOV version 1.14