| /*------------------------------------------------------------------------- |
| * |
| * 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 three mechanisms for minimizing this cost: |
| * 1. We rely on the typcache to keep up-to-date copies of the constraints. |
| * 2. In a nest of domains, we flatten the checking of all the levels |
| * into just one operation (the typcache does this for us). |
| * 3. If there are CHECK constraints, we cache a standalone ExprContext |
| * to evaluate them in. |
| * |
| * |
| * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * |
| * IDENTIFICATION |
| * src/backend/utils/adt/domains.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "access/htup_details.h" |
| #include "catalog/pg_type.h" |
| #include "executor/executor.h" |
| #include "lib/stringinfo.h" |
| #include "utils/builtins.h" |
| #include "utils/expandeddatum.h" |
| #include "utils/lsyscache.h" |
| #include "utils/syscache.h" |
| #include "utils/typcache.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; |
| /* Reference to cached list of constraint items to check */ |
| DomainConstraintRef constraint_ref; |
| /* Context for evaluating CHECK constraints in */ |
| ExprContext *econtext; |
| /* Memory context this cache is in */ |
| MemoryContext mcxt; |
| } DomainIOData; |
| |
| |
| /* |
| * domain_state_setup - initialize the cache for a new domain type. |
| * |
| * Note: we can't re-use the same cache struct for a new domain type, |
| * since there's no provision for releasing the DomainConstraintRef. |
| * If a call site needs to deal with a new domain type, we just leak |
| * the old struct for the duration of the query. |
| */ |
| static DomainIOData * |
| domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt) |
| { |
| DomainIOData *my_extra; |
| TypeCacheEntry *typentry; |
| Oid baseType; |
| |
| my_extra = (DomainIOData *) MemoryContextAlloc(mcxt, sizeof(DomainIOData)); |
| |
| /* |
| * Verify that domainType represents a valid domain type. We need to be |
| * careful here because domain_in and domain_recv can be called from SQL, |
| * possibly with incorrect arguments. We use lookup_type_cache mainly |
| * because it will throw a clean user-facing error for a bad OID; but also |
| * it can cache the underlying base type info. |
| */ |
| typentry = lookup_type_cache(domainType, TYPECACHE_DOMAIN_BASE_INFO); |
| if (typentry->typtype != TYPTYPE_DOMAIN) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("type %s is not a domain", |
| format_type_be(domainType)))); |
| |
| /* Find out the base type */ |
| baseType = typentry->domainBaseType; |
| my_extra->typtypmod = typentry->domainBaseTypmod; |
| |
| /* 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 */ |
| InitDomainConstraintRef(domainType, &my_extra->constraint_ref, mcxt, true); |
| |
| /* We don't make an ExprContext until needed */ |
| my_extra->econtext = NULL; |
| my_extra->mcxt = mcxt; |
| |
| /* Mark cache valid */ |
| my_extra->domain_type = domainType; |
| |
| return my_extra; |
| } |
| |
| /* |
| * domain_check_input - apply the cached checks. |
| * |
| * This is roughly similar to the handling of CoerceToDomain nodes in |
| * execExpr*.c, but we execute each constraint separately, rather than |
| * compiling them in-line within a larger expression. |
| * |
| * If escontext points to an ErrorSaveContext, any failures are reported |
| * there, otherwise they are ereport'ed. Note that we do not attempt to do |
| * soft reporting of errors raised during execution of CHECK constraints. |
| */ |
| static void |
| domain_check_input(Datum value, bool isnull, DomainIOData *my_extra, |
| Node *escontext) |
| { |
| ExprContext *econtext = my_extra->econtext; |
| ListCell *l; |
| |
| /* Make sure we have up-to-date constraints */ |
| UpdateDomainConstraintRef(&my_extra->constraint_ref); |
| |
| foreach(l, my_extra->constraint_ref.constraints) |
| { |
| DomainConstraintState *con = (DomainConstraintState *) lfirst(l); |
| |
| switch (con->constrainttype) |
| { |
| case DOM_CONSTRAINT_NOTNULL: |
| if (isnull) |
| { |
| errsave(escontext, |
| (errcode(ERRCODE_NOT_NULL_VIOLATION), |
| errmsg("domain %s does not allow null values", |
| format_type_be(my_extra->domain_type)), |
| errdatatype(my_extra->domain_type))); |
| goto fail; |
| } |
| break; |
| case DOM_CONSTRAINT_CHECK: |
| { |
| /* Make the econtext if we didn't already */ |
| if (econtext == NULL) |
| { |
| MemoryContext oldcontext; |
| |
| oldcontext = MemoryContextSwitchTo(my_extra->mcxt); |
| econtext = CreateStandaloneExprContext(); |
| MemoryContextSwitchTo(oldcontext); |
| my_extra->econtext = econtext; |
| } |
| |
| /* |
| * Set up value to be returned by CoerceToDomainValue |
| * nodes. Unlike in the generic expression case, this |
| * econtext couldn't be shared with anything else, so no |
| * need to save and restore fields. But we do need to |
| * protect the passed-in value against being changed by |
| * called functions. (It couldn't be a R/W expanded |
| * object for most uses, but that seems possible for |
| * domain_check().) |
| */ |
| econtext->domainValue_datum = |
| MakeExpandedObjectReadOnly(value, isnull, |
| my_extra->constraint_ref.tcache->typlen); |
| econtext->domainValue_isNull = isnull; |
| |
| if (!ExecCheck(con->check_exprstate, econtext)) |
| { |
| errsave(escontext, |
| (errcode(ERRCODE_CHECK_VIOLATION), |
| errmsg("value for domain %s violates check constraint \"%s\"", |
| format_type_be(my_extra->domain_type), |
| con->name), |
| errdomainconstraint(my_extra->domain_type, |
| con->name))); |
| goto fail; |
| } |
| break; |
| } |
| default: |
| elog(ERROR, "unrecognized constraint type: %d", |
| (int) con->constrainttype); |
| break; |
| } |
| } |
| |
| /* |
| * Before exiting, call any shutdown callbacks and reset econtext's |
| * per-tuple memory. This avoids leaking non-memory resources, if |
| * anything in the expression(s) has any. |
| */ |
| fail: |
| if (econtext) |
| ReScanExprContext(econtext); |
| } |
| |
| |
| /* |
| * domain_in - input routine for any domain type. |
| */ |
| Datum |
| domain_in(PG_FUNCTION_ARGS) |
| { |
| char *string; |
| Oid domainType; |
| Node *escontext = fcinfo->context; |
| 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 (which really |
| * shouldn't happen, but cope if it does). |
| */ |
| my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra; |
| if (my_extra == NULL || my_extra->domain_type != domainType) |
| { |
| my_extra = domain_state_setup(domainType, false, |
| fcinfo->flinfo->fn_mcxt); |
| fcinfo->flinfo->fn_extra = (void *) my_extra; |
| } |
| |
| /* |
| * Invoke the base type's typinput procedure to convert the data. |
| */ |
| if (!InputFunctionCallSafe(&my_extra->proc, |
| string, |
| my_extra->typioparam, |
| my_extra->typtypmod, |
| escontext, |
| &value)) |
| PG_RETURN_NULL(); |
| |
| /* |
| * Do the necessary checks to ensure it's a valid domain value. |
| */ |
| domain_check_input(value, (string == NULL), my_extra, escontext); |
| |
| 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 (which really |
| * shouldn't happen, but cope if it does). |
| */ |
| my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra; |
| if (my_extra == NULL || my_extra->domain_type != domainType) |
| { |
| my_extra = domain_state_setup(domainType, true, |
| fcinfo->flinfo->fn_mcxt); |
| fcinfo->flinfo->fn_extra = (void *) my_extra; |
| } |
| |
| /* |
| * 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, NULL); |
| |
| if (buf == NULL) |
| PG_RETURN_NULL(); |
| else |
| PG_RETURN_DATUM(value); |
| } |
| |
| /* |
| * domain_check - check that a datum satisfies the constraints of a |
| * domain. extra and mcxt can be passed if they are available from, |
| * say, a FmgrInfo structure, or they can be NULL, in which case the |
| * setup is repeated for each call. |
| */ |
| void |
| domain_check(Datum value, bool isnull, Oid domainType, |
| void **extra, MemoryContext mcxt) |
| { |
| DomainIOData *my_extra = NULL; |
| |
| if (mcxt == NULL) |
| mcxt = CurrentMemoryContext; |
| |
| /* |
| * We arrange to look up the needed info just once per series of calls, |
| * assuming the domain type doesn't change underneath us (which really |
| * shouldn't happen, but cope if it does). |
| */ |
| if (extra) |
| my_extra = (DomainIOData *) *extra; |
| if (my_extra == NULL || my_extra->domain_type != domainType) |
| { |
| my_extra = domain_state_setup(domainType, true, mcxt); |
| if (extra) |
| *extra = (void *) my_extra; |
| } |
| |
| /* |
| * Do the necessary checks to ensure it's a valid domain value. |
| */ |
| domain_check_input(value, isnull, my_extra, NULL); |
| } |
| |
| /* |
| * errdatatype --- stores schema_name and datatype_name of a datatype |
| * within the current errordata. |
| */ |
| int |
| errdatatype(Oid datatypeOid) |
| { |
| HeapTuple tup; |
| Form_pg_type typtup; |
| |
| tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(datatypeOid)); |
| if (!HeapTupleIsValid(tup)) |
| elog(ERROR, "cache lookup failed for type %u", datatypeOid); |
| typtup = (Form_pg_type) GETSTRUCT(tup); |
| |
| err_generic_string(PG_DIAG_SCHEMA_NAME, |
| get_namespace_name(typtup->typnamespace)); |
| err_generic_string(PG_DIAG_DATATYPE_NAME, NameStr(typtup->typname)); |
| |
| ReleaseSysCache(tup); |
| |
| return 0; /* return value does not matter */ |
| } |
| |
| /* |
| * errdomainconstraint --- stores schema_name, datatype_name and |
| * constraint_name of a domain-related constraint within the current errordata. |
| */ |
| int |
| errdomainconstraint(Oid datatypeOid, const char *conname) |
| { |
| errdatatype(datatypeOid); |
| err_generic_string(PG_DIAG_CONSTRAINT_NAME, conname); |
| |
| return 0; /* return value does not matter */ |
| } |