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 */
47typedef 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 */
72static DomainIOData *
73domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt)
74{
75 DomainIOData *my_extra;
76 TypeCacheEntry *typentry;
77 Oid baseType;
78
79 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 typentry = lookup_type_cache(domainType, TYPECACHE_DOMAIN_BASE_INFO);
89 if (typentry->typtype != TYPTYPE_DOMAIN)
90 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 baseType = typentry->domainBaseType;
97 my_extra->typtypmod = typentry->domainBaseTypmod;
98
99 /* Look up underlying I/O function */
100 if (binary)
101 getTypeBinaryInputInfo(baseType,
102 &my_extra->typiofunc,
103 &my_extra->typioparam);
104 else
105 getTypeInputInfo(baseType,
106 &my_extra->typiofunc,
107 &my_extra->typioparam);
108 fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, mcxt);
109
110 /* Look up constraints for domain */
111 InitDomainConstraintRef(domainType, &my_extra->constraint_ref, mcxt, true);
112
113 /* We don't make an ExprContext until needed */
114 my_extra->econtext = NULL;
115 my_extra->mcxt = mcxt;
116
117 /* Mark cache valid */
118 my_extra->domain_type = domainType;
119
120 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 */
130static void
131domain_check_input(Datum value, bool isnull, DomainIOData *my_extra)
132{
133 ExprContext *econtext = my_extra->econtext;
134 ListCell *l;
135
136 /* Make sure we have up-to-date constraints */
137 UpdateDomainConstraintRef(&my_extra->constraint_ref);
138
139 foreach(l, my_extra->constraint_ref.constraints)
140 {
141 DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
142
143 switch (con->constrainttype)
144 {
145 case DOM_CONSTRAINT_NOTNULL:
146 if (isnull)
147 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 break;
153 case DOM_CONSTRAINT_CHECK:
154 {
155 /* Make the econtext if we didn't already */
156 if (econtext == NULL)
157 {
158 MemoryContext oldcontext;
159
160 oldcontext = MemoryContextSwitchTo(my_extra->mcxt);
161 econtext = CreateStandaloneExprContext();
162 MemoryContextSwitchTo(oldcontext);
163 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 econtext->domainValue_datum =
177 MakeExpandedObjectReadOnly(value, isnull,
178 my_extra->constraint_ref.tcache->typlen);
179 econtext->domainValue_isNull = isnull;
180
181 if (!ExecCheck(con->check_exprstate, econtext))
182 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 break;
190 }
191 default:
192 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 if (econtext)
204 ReScanExprContext(econtext);
205}
206
207
208/*
209 * domain_in - input routine for any domain type.
210 */
211Datum
212domain_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 if (PG_ARGISNULL(0))
225 string = NULL;
226 else
227 string = PG_GETARG_CSTRING(0);
228 if (PG_ARGISNULL(1))
229 PG_RETURN_NULL();
230 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 my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra;
238 if (my_extra == NULL || my_extra->domain_type != domainType)
239 {
240 my_extra = domain_state_setup(domainType, false,
241 fcinfo->flinfo->fn_mcxt);
242 fcinfo->flinfo->fn_extra = (void *) my_extra;
243 }
244
245 /*
246 * Invoke the base type's typinput procedure to convert the data.
247 */
248 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 domain_check_input(value, (string == NULL), my_extra);
257
258 if (string == NULL)
259 PG_RETURN_NULL();
260 else
261 PG_RETURN_DATUM(value);
262}
263
264/*
265 * domain_recv - binary input routine for any domain type.
266 */
267Datum
268domain_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 if (PG_ARGISNULL(0))
281 buf = NULL;
282 else
283 buf = (StringInfo) PG_GETARG_POINTER(0);
284 if (PG_ARGISNULL(1))
285 PG_RETURN_NULL();
286 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 my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra;
294 if (my_extra == NULL || my_extra->domain_type != domainType)
295 {
296 my_extra = domain_state_setup(domainType, true,
297 fcinfo->flinfo->fn_mcxt);
298 fcinfo->flinfo->fn_extra = (void *) my_extra;
299 }
300
301 /*
302 * Invoke the base type's typreceive procedure to convert the data.
303 */
304 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 domain_check_input(value, (buf == NULL), my_extra);
313
314 if (buf == NULL)
315 PG_RETURN_NULL();
316 else
317 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 */
326void
327domain_check(Datum value, bool isnull, Oid domainType,
328 void **extra, MemoryContext mcxt)
329{
330 DomainIOData *my_extra = NULL;
331
332 if (mcxt == NULL)
333 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 if (extra)
341 my_extra = (DomainIOData *) *extra;
342 if (my_extra == NULL || my_extra->domain_type != domainType)
343 {
344 my_extra = domain_state_setup(domainType, true, mcxt);
345 if (extra)
346 *extra = (void *) my_extra;
347 }
348
349 /*
350 * Do the necessary checks to ensure it's a valid domain value.
351 */
352 domain_check_input(value, isnull, my_extra);
353}
354
355/*
356 * errdatatype --- stores schema_name and datatype_name of a datatype
357 * within the current errordata.
358 */
359int
360errdatatype(Oid datatypeOid)
361{
362 HeapTuple tup;
363 Form_pg_type typtup;
364
365 tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(datatypeOid));
366 if (!HeapTupleIsValid(tup))
367 elog(ERROR, "cache lookup failed for type %u", datatypeOid);
368 typtup = (Form_pg_type) GETSTRUCT(tup);
369
370 err_generic_string(PG_DIAG_SCHEMA_NAME,
371 get_namespace_name(typtup->typnamespace));
372 err_generic_string(PG_DIAG_DATATYPE_NAME, NameStr(typtup->typname));
373
374 ReleaseSysCache(tup);
375
376 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 */
383int
384errdomainconstraint(Oid datatypeOid, const char *conname)
385{
386 errdatatype(datatypeOid);
387 err_generic_string(PG_DIAG_CONSTRAINT_NAME, conname);
388
389 return 0; /* return value does not matter */
390}
391