summaryrefslogtreecommitdiffstats
path: root/src/backend/utils/adt/domains.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/adt/domains.c')
-rw-r--r--src/backend/utils/adt/domains.c390
1 files changed, 390 insertions, 0 deletions
diff --git a/src/backend/utils/adt/domains.c b/src/backend/utils/adt/domains.c
new file mode 100644
index 0000000..0a36772
--- /dev/null
+++ b/src/backend/utils/adt/domains.c
@@ -0,0 +1,390 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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-2021, 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.
+ */
+static void
+domain_check_input(Datum value, bool isnull, DomainIOData *my_extra)
+{
+ 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)
+ ereport(ERROR,
+ (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)));
+ 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))
+ ereport(ERROR,
+ (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)));
+ 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.
+ */
+ if (econtext)
+ ReScanExprContext(econtext);
+}
+
+
+/*
+ * 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 (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.
+ */
+ 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 (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);
+
+ 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);
+}
+
+/*
+ * 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 */
+}