LCOV - code coverage report
Current view: top level - src/backend/utils/adt - domains.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 91 113 80.5 %
Date: 2024-03-29 15:11:20 Functions: 8 9 88.9 %
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-2024, 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             : static bool domain_check_internal(Datum value, bool isnull, Oid domainType,
      44             :                                   void **extra, MemoryContext mcxt,
      45             :                                   Node *escontext);
      46             : 
      47             : /*
      48             :  * structure to cache state across multiple calls
      49             :  */
      50             : typedef struct DomainIOData
      51             : {
      52             :     Oid         domain_type;
      53             :     /* Data needed to call base type's input function */
      54             :     Oid         typiofunc;
      55             :     Oid         typioparam;
      56             :     int32       typtypmod;
      57             :     FmgrInfo    proc;
      58             :     /* Reference to cached list of constraint items to check */
      59             :     DomainConstraintRef constraint_ref;
      60             :     /* Context for evaluating CHECK constraints in */
      61             :     ExprContext *econtext;
      62             :     /* Memory context this cache is in */
      63             :     MemoryContext mcxt;
      64             : } DomainIOData;
      65             : 
      66             : 
      67             : /*
      68             :  * domain_state_setup - initialize the cache for a new domain type.
      69             :  *
      70             :  * Note: we can't re-use the same cache struct for a new domain type,
      71             :  * since there's no provision for releasing the DomainConstraintRef.
      72             :  * If a call site needs to deal with a new domain type, we just leak
      73             :  * the old struct for the duration of the query.
      74             :  */
      75             : static DomainIOData *
      76        2968 : domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt)
      77             : {
      78             :     DomainIOData *my_extra;
      79             :     TypeCacheEntry *typentry;
      80             :     Oid         baseType;
      81             : 
      82        2968 :     my_extra = (DomainIOData *) MemoryContextAlloc(mcxt, sizeof(DomainIOData));
      83             : 
      84             :     /*
      85             :      * Verify that domainType represents a valid domain type.  We need to be
      86             :      * careful here because domain_in and domain_recv can be called from SQL,
      87             :      * possibly with incorrect arguments.  We use lookup_type_cache mainly
      88             :      * because it will throw a clean user-facing error for a bad OID; but also
      89             :      * it can cache the underlying base type info.
      90             :      */
      91        2968 :     typentry = lookup_type_cache(domainType, TYPECACHE_DOMAIN_BASE_INFO);
      92        2968 :     if (typentry->typtype != TYPTYPE_DOMAIN)
      93           0 :         ereport(ERROR,
      94             :                 (errcode(ERRCODE_DATATYPE_MISMATCH),
      95             :                  errmsg("type %s is not a domain",
      96             :                         format_type_be(domainType))));
      97             : 
      98             :     /* Find out the base type */
      99        2968 :     baseType = typentry->domainBaseType;
     100        2968 :     my_extra->typtypmod = typentry->domainBaseTypmod;
     101             : 
     102             :     /* Look up underlying I/O function */
     103        2968 :     if (binary)
     104        2150 :         getTypeBinaryInputInfo(baseType,
     105             :                                &my_extra->typiofunc,
     106             :                                &my_extra->typioparam);
     107             :     else
     108         818 :         getTypeInputInfo(baseType,
     109             :                          &my_extra->typiofunc,
     110             :                          &my_extra->typioparam);
     111        2968 :     fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, mcxt);
     112             : 
     113             :     /* Look up constraints for domain */
     114        2968 :     InitDomainConstraintRef(domainType, &my_extra->constraint_ref, mcxt, true);
     115             : 
     116             :     /* We don't make an ExprContext until needed */
     117        2968 :     my_extra->econtext = NULL;
     118        2968 :     my_extra->mcxt = mcxt;
     119             : 
     120             :     /* Mark cache valid */
     121        2968 :     my_extra->domain_type = domainType;
     122             : 
     123        2968 :     return my_extra;
     124             : }
     125             : 
     126             : /*
     127             :  * domain_check_input - apply the cached checks.
     128             :  *
     129             :  * This is roughly similar to the handling of CoerceToDomain nodes in
     130             :  * execExpr*.c, but we execute each constraint separately, rather than
     131             :  * compiling them in-line within a larger expression.
     132             :  *
     133             :  * If escontext points to an ErrorSaveContext, any failures are reported
     134             :  * there, otherwise they are ereport'ed.  Note that we do not attempt to do
     135             :  * soft reporting of errors raised during execution of CHECK constraints.
     136             :  */
     137             : static void
     138      347744 : domain_check_input(Datum value, bool isnull, DomainIOData *my_extra,
     139             :                    Node *escontext)
     140             : {
     141      347744 :     ExprContext *econtext = my_extra->econtext;
     142             :     ListCell   *l;
     143             : 
     144             :     /* Make sure we have up-to-date constraints */
     145      347744 :     UpdateDomainConstraintRef(&my_extra->constraint_ref);
     146             : 
     147      408162 :     foreach(l, my_extra->constraint_ref.constraints)
     148             :     {
     149       60658 :         DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
     150             : 
     151       60658 :         switch (con->constrainttype)
     152             :         {
     153         210 :             case DOM_CONSTRAINT_NOTNULL:
     154         210 :                 if (isnull)
     155             :                 {
     156          86 :                     errsave(escontext,
     157             :                             (errcode(ERRCODE_NOT_NULL_VIOLATION),
     158             :                              errmsg("domain %s does not allow null values",
     159             :                                     format_type_be(my_extra->domain_type)),
     160             :                              errdatatype(my_extra->domain_type)));
     161          42 :                     goto fail;
     162             :                 }
     163         124 :                 break;
     164       60448 :             case DOM_CONSTRAINT_CHECK:
     165             :                 {
     166             :                     /* Make the econtext if we didn't already */
     167       60448 :                     if (econtext == NULL)
     168             :                     {
     169             :                         MemoryContext oldcontext;
     170             : 
     171        2240 :                         oldcontext = MemoryContextSwitchTo(my_extra->mcxt);
     172        2240 :                         econtext = CreateStandaloneExprContext();
     173        2240 :                         MemoryContextSwitchTo(oldcontext);
     174        2240 :                         my_extra->econtext = econtext;
     175             :                     }
     176             : 
     177             :                     /*
     178             :                      * Set up value to be returned by CoerceToDomainValue
     179             :                      * nodes.  Unlike in the generic expression case, this
     180             :                      * econtext couldn't be shared with anything else, so no
     181             :                      * need to save and restore fields.  But we do need to
     182             :                      * protect the passed-in value against being changed by
     183             :                      * called functions.  (It couldn't be a R/W expanded
     184             :                      * object for most uses, but that seems possible for
     185             :                      * domain_check().)
     186             :                      */
     187       60448 :                     econtext->domainValue_datum =
     188       60448 :                         MakeExpandedObjectReadOnly(value, isnull,
     189             :                                                    my_extra->constraint_ref.tcache->typlen);
     190       60448 :                     econtext->domainValue_isNull = isnull;
     191             : 
     192       60448 :                     if (!ExecCheck(con->check_exprstate, econtext))
     193             :                     {
     194         148 :                         errsave(escontext,
     195             :                                 (errcode(ERRCODE_CHECK_VIOLATION),
     196             :                                  errmsg("value for domain %s violates check constraint \"%s\"",
     197             :                                         format_type_be(my_extra->domain_type),
     198             :                                         con->name),
     199             :                                  errdomainconstraint(my_extra->domain_type,
     200             :                                                      con->name)));
     201          24 :                         goto fail;
     202             :                     }
     203       60294 :                     break;
     204             :                 }
     205           0 :             default:
     206           0 :                 elog(ERROR, "unrecognized constraint type: %d",
     207             :                      (int) con->constrainttype);
     208             :                 break;
     209             :         }
     210             :     }
     211             : 
     212             :     /*
     213             :      * Before exiting, call any shutdown callbacks and reset econtext's
     214             :      * per-tuple memory.  This avoids leaking non-memory resources, if
     215             :      * anything in the expression(s) has any.
     216             :      */
     217      347546 : fail:
     218      347546 :     if (econtext)
     219       60318 :         ReScanExprContext(econtext);
     220      347546 : }
     221             : 
     222             : 
     223             : /*
     224             :  * domain_in        - input routine for any domain type.
     225             :  */
     226             : Datum
     227      344732 : domain_in(PG_FUNCTION_ARGS)
     228             : {
     229             :     char       *string;
     230             :     Oid         domainType;
     231      344732 :     Node       *escontext = fcinfo->context;
     232             :     DomainIOData *my_extra;
     233             :     Datum       value;
     234             : 
     235             :     /*
     236             :      * Since domain_in is not strict, we have to check for null inputs. The
     237             :      * typioparam argument should never be null in normal system usage, but it
     238             :      * could be null in a manual invocation --- if so, just return null.
     239             :      */
     240      344732 :     if (PG_ARGISNULL(0))
     241         110 :         string = NULL;
     242             :     else
     243      344622 :         string = PG_GETARG_CSTRING(0);
     244      344732 :     if (PG_ARGISNULL(1))
     245           0 :         PG_RETURN_NULL();
     246      344732 :     domainType = PG_GETARG_OID(1);
     247             : 
     248             :     /*
     249             :      * We arrange to look up the needed info just once per series of calls,
     250             :      * assuming the domain type doesn't change underneath us (which really
     251             :      * shouldn't happen, but cope if it does).
     252             :      */
     253      344732 :     my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra;
     254      344732 :     if (my_extra == NULL || my_extra->domain_type != domainType)
     255             :     {
     256         818 :         my_extra = domain_state_setup(domainType, false,
     257         818 :                                       fcinfo->flinfo->fn_mcxt);
     258         818 :         fcinfo->flinfo->fn_extra = (void *) my_extra;
     259             :     }
     260             : 
     261             :     /*
     262             :      * Invoke the base type's typinput procedure to convert the data.
     263             :      */
     264      344732 :     if (!InputFunctionCallSafe(&my_extra->proc,
     265             :                                string,
     266             :                                my_extra->typioparam,
     267             :                                my_extra->typtypmod,
     268             :                                escontext,
     269             :                                &value))
     270          18 :         PG_RETURN_NULL();
     271             : 
     272             :     /*
     273             :      * Do the necessary checks to ensure it's a valid domain value.
     274             :      */
     275      344702 :     domain_check_input(value, (string == NULL), my_extra, escontext);
     276             : 
     277      344662 :     if (string == NULL)
     278         138 :         PG_RETURN_NULL();
     279             :     else
     280      344524 :         PG_RETURN_DATUM(value);
     281             : }
     282             : 
     283             : /*
     284             :  * domain_recv      - binary input routine for any domain type.
     285             :  */
     286             : Datum
     287           0 : domain_recv(PG_FUNCTION_ARGS)
     288             : {
     289             :     StringInfo  buf;
     290             :     Oid         domainType;
     291             :     DomainIOData *my_extra;
     292             :     Datum       value;
     293             : 
     294             :     /*
     295             :      * Since domain_recv is not strict, we have to check for null inputs. The
     296             :      * typioparam argument should never be null in normal system usage, but it
     297             :      * could be null in a manual invocation --- if so, just return null.
     298             :      */
     299           0 :     if (PG_ARGISNULL(0))
     300           0 :         buf = NULL;
     301             :     else
     302           0 :         buf = (StringInfo) PG_GETARG_POINTER(0);
     303           0 :     if (PG_ARGISNULL(1))
     304           0 :         PG_RETURN_NULL();
     305           0 :     domainType = PG_GETARG_OID(1);
     306             : 
     307             :     /*
     308             :      * We arrange to look up the needed info just once per series of calls,
     309             :      * assuming the domain type doesn't change underneath us (which really
     310             :      * shouldn't happen, but cope if it does).
     311             :      */
     312           0 :     my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra;
     313           0 :     if (my_extra == NULL || my_extra->domain_type != domainType)
     314             :     {
     315           0 :         my_extra = domain_state_setup(domainType, true,
     316           0 :                                       fcinfo->flinfo->fn_mcxt);
     317           0 :         fcinfo->flinfo->fn_extra = (void *) my_extra;
     318             :     }
     319             : 
     320             :     /*
     321             :      * Invoke the base type's typreceive procedure to convert the data.
     322             :      */
     323           0 :     value = ReceiveFunctionCall(&my_extra->proc,
     324             :                                 buf,
     325             :                                 my_extra->typioparam,
     326             :                                 my_extra->typtypmod);
     327             : 
     328             :     /*
     329             :      * Do the necessary checks to ensure it's a valid domain value.
     330             :      */
     331           0 :     domain_check_input(value, (buf == NULL), my_extra, NULL);
     332             : 
     333           0 :     if (buf == NULL)
     334           0 :         PG_RETURN_NULL();
     335             :     else
     336           0 :         PG_RETURN_DATUM(value);
     337             : }
     338             : 
     339             : /*
     340             :  * domain_check - check that a datum satisfies the constraints of a
     341             :  * domain.  extra and mcxt can be passed if they are available from,
     342             :  * say, a FmgrInfo structure, or they can be NULL, in which case the
     343             :  * setup is repeated for each call.
     344             :  */
     345             : void
     346         198 : domain_check(Datum value, bool isnull, Oid domainType,
     347             :              void **extra, MemoryContext mcxt)
     348             : {
     349         198 :     (void) domain_check_internal(value, isnull, domainType, extra, mcxt,
     350             :                                  NULL);
     351         130 : }
     352             : 
     353             : /* Error-safe variant of domain_check(). */
     354             : bool
     355        2844 : domain_check_safe(Datum value, bool isnull, Oid domainType,
     356             :                   void **extra, MemoryContext mcxt,
     357             :                   Node *escontext)
     358             : {
     359        2844 :     return domain_check_internal(value, isnull, domainType, extra, mcxt,
     360             :                                  escontext);
     361             : }
     362             : 
     363             : /*
     364             :  * domain_check_internal
     365             :  *      Workhorse for domain_check() and domain_check_safe()
     366             :  *
     367             :  * Returns false if an error occurred in domain_check_input() and 'escontext'
     368             :  * points to an ErrorSaveContext, true otherwise.
     369             :  */
     370             : static bool
     371        3042 : domain_check_internal(Datum value, bool isnull, Oid domainType,
     372             :                       void **extra, MemoryContext mcxt,
     373             :                       Node *escontext)
     374             : {
     375        3042 :     DomainIOData *my_extra = NULL;
     376             : 
     377        3042 :     if (mcxt == NULL)
     378          24 :         mcxt = CurrentMemoryContext;
     379             : 
     380             :     /*
     381             :      * We arrange to look up the needed info just once per series of calls,
     382             :      * assuming the domain type doesn't change underneath us (which really
     383             :      * shouldn't happen, but cope if it does).
     384             :      */
     385        3042 :     if (extra)
     386        3018 :         my_extra = (DomainIOData *) *extra;
     387        3042 :     if (my_extra == NULL || my_extra->domain_type != domainType)
     388             :     {
     389        2150 :         my_extra = domain_state_setup(domainType, true, mcxt);
     390        2150 :         if (extra)
     391        2126 :             *extra = (void *) my_extra;
     392             :     }
     393             : 
     394             :     /*
     395             :      * Do the necessary checks to ensure it's a valid domain value.
     396             :      */
     397        3042 :     domain_check_input(value, isnull, my_extra, escontext);
     398             : 
     399        2884 :     return !SOFT_ERROR_OCCURRED(escontext);
     400             : }
     401             : 
     402             : /*
     403             :  * errdatatype --- stores schema_name and datatype_name of a datatype
     404             :  * within the current errordata.
     405             :  */
     406             : int
     407         738 : errdatatype(Oid datatypeOid)
     408             : {
     409             :     HeapTuple   tup;
     410             :     Form_pg_type typtup;
     411             : 
     412         738 :     tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(datatypeOid));
     413         738 :     if (!HeapTupleIsValid(tup))
     414           0 :         elog(ERROR, "cache lookup failed for type %u", datatypeOid);
     415         738 :     typtup = (Form_pg_type) GETSTRUCT(tup);
     416             : 
     417         738 :     err_generic_string(PG_DIAG_SCHEMA_NAME,
     418         738 :                        get_namespace_name(typtup->typnamespace));
     419         738 :     err_generic_string(PG_DIAG_DATATYPE_NAME, NameStr(typtup->typname));
     420             : 
     421         738 :     ReleaseSysCache(tup);
     422             : 
     423         738 :     return 0;                   /* return value does not matter */
     424             : }
     425             : 
     426             : /*
     427             :  * errdomainconstraint --- stores schema_name, datatype_name and
     428             :  * constraint_name of a domain-related constraint within the current errordata.
     429             :  */
     430             : int
     431         558 : errdomainconstraint(Oid datatypeOid, const char *conname)
     432             : {
     433         558 :     errdatatype(datatypeOid);
     434         558 :     err_generic_string(PG_DIAG_CONSTRAINT_NAME, conname);
     435             : 
     436         558 :     return 0;                   /* return value does not matter */
     437             : }

Generated by: LCOV version 1.14