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 3172 : domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt)
77 : {
78 : DomainIOData *my_extra;
79 : TypeCacheEntry *typentry;
80 : Oid baseType;
81 :
82 3172 : 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 3172 : typentry = lookup_type_cache(domainType, TYPECACHE_DOMAIN_BASE_INFO);
92 3172 : 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 3172 : baseType = typentry->domainBaseType;
100 3172 : my_extra->typtypmod = typentry->domainBaseTypmod;
101 :
102 : /* Look up underlying I/O function */
103 3172 : if (binary)
104 2270 : getTypeBinaryInputInfo(baseType,
105 : &my_extra->typiofunc,
106 : &my_extra->typioparam);
107 : else
108 902 : getTypeInputInfo(baseType,
109 : &my_extra->typiofunc,
110 : &my_extra->typioparam);
111 3172 : fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, mcxt);
112 :
113 : /* Look up constraints for domain */
114 3172 : InitDomainConstraintRef(domainType, &my_extra->constraint_ref, mcxt, true);
115 :
116 : /* We don't make an ExprContext until needed */
117 3172 : my_extra->econtext = NULL;
118 3172 : my_extra->mcxt = mcxt;
119 :
120 : /* Mark cache valid */
121 3172 : my_extra->domain_type = domainType;
122 :
123 3172 : 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 393260 : domain_check_input(Datum value, bool isnull, DomainIOData *my_extra,
139 : Node *escontext)
140 : {
141 393260 : ExprContext *econtext = my_extra->econtext;
142 : ListCell *l;
143 :
144 : /* Make sure we have up-to-date constraints */
145 393260 : UpdateDomainConstraintRef(&my_extra->constraint_ref);
146 :
147 461348 : foreach(l, my_extra->constraint_ref.constraints)
148 : {
149 68406 : DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
150 :
151 68406 : switch (con->constrainttype)
152 : {
153 258 : case DOM_CONSTRAINT_NOTNULL:
154 258 : if (isnull)
155 : {
156 116 : 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 96 : goto fail;
162 : }
163 142 : break;
164 68148 : case DOM_CONSTRAINT_CHECK:
165 : {
166 : /* Make the econtext if we didn't already */
167 68148 : if (econtext == NULL)
168 : {
169 : MemoryContext oldcontext;
170 :
171 2328 : oldcontext = MemoryContextSwitchTo(my_extra->mcxt);
172 2328 : econtext = CreateStandaloneExprContext();
173 2328 : MemoryContextSwitchTo(oldcontext);
174 2328 : 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 68148 : econtext->domainValue_datum =
188 68148 : MakeExpandedObjectReadOnly(value, isnull,
189 : my_extra->constraint_ref.tcache->typlen);
190 68148 : econtext->domainValue_isNull = isnull;
191 :
192 68148 : if (!ExecCheck(con->check_exprstate, econtext))
193 : {
194 196 : 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 60 : goto fail;
202 : }
203 67946 : 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 393038 : fail:
218 393038 : if (econtext)
219 68006 : ReScanExprContext(econtext);
220 393038 : }
221 :
222 :
223 : /*
224 : * domain_in - input routine for any domain type.
225 : */
226 : Datum
227 390068 : domain_in(PG_FUNCTION_ARGS)
228 : {
229 : char *string;
230 : Oid domainType;
231 390068 : 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 390068 : if (PG_ARGISNULL(0))
241 110 : string = NULL;
242 : else
243 389958 : string = PG_GETARG_CSTRING(0);
244 390068 : if (PG_ARGISNULL(1))
245 0 : PG_RETURN_NULL();
246 390068 : 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 390068 : my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra;
254 390068 : if (my_extra == NULL || my_extra->domain_type != domainType)
255 : {
256 902 : my_extra = domain_state_setup(domainType, false,
257 902 : fcinfo->flinfo->fn_mcxt);
258 902 : fcinfo->flinfo->fn_extra = (void *) my_extra;
259 : }
260 :
261 : /*
262 : * Invoke the base type's typinput procedure to convert the data.
263 : */
264 390068 : if (!InputFunctionCallSafe(&my_extra->proc,
265 : string,
266 : my_extra->typioparam,
267 : my_extra->typtypmod,
268 : escontext,
269 : &value))
270 24 : PG_RETURN_NULL();
271 :
272 : /*
273 : * Do the necessary checks to ensure it's a valid domain value.
274 : */
275 390026 : domain_check_input(value, (string == NULL), my_extra, escontext);
276 :
277 389986 : if (string == NULL)
278 150 : PG_RETURN_NULL();
279 : else
280 389836 : 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 3036 : domain_check_safe(Datum value, bool isnull, Oid domainType,
356 : void **extra, MemoryContext mcxt,
357 : Node *escontext)
358 : {
359 3036 : 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 3234 : domain_check_internal(Datum value, bool isnull, Oid domainType,
372 : void **extra, MemoryContext mcxt,
373 : Node *escontext)
374 : {
375 3234 : DomainIOData *my_extra = NULL;
376 :
377 3234 : 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 3234 : if (extra)
386 3210 : my_extra = (DomainIOData *) *extra;
387 3234 : if (my_extra == NULL || my_extra->domain_type != domainType)
388 : {
389 2270 : my_extra = domain_state_setup(domainType, true, mcxt);
390 2270 : if (extra)
391 2246 : *extra = (void *) my_extra;
392 : }
393 :
394 : /*
395 : * Do the necessary checks to ensure it's a valid domain value.
396 : */
397 3234 : domain_check_input(value, isnull, my_extra, escontext);
398 :
399 3052 : 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 792 : errdatatype(Oid datatypeOid)
408 : {
409 : HeapTuple tup;
410 : Form_pg_type typtup;
411 :
412 792 : tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(datatypeOid));
413 792 : if (!HeapTupleIsValid(tup))
414 0 : elog(ERROR, "cache lookup failed for type %u", datatypeOid);
415 792 : typtup = (Form_pg_type) GETSTRUCT(tup);
416 :
417 792 : err_generic_string(PG_DIAG_SCHEMA_NAME,
418 792 : get_namespace_name(typtup->typnamespace));
419 792 : err_generic_string(PG_DIAG_DATATYPE_NAME, NameStr(typtup->typname));
420 :
421 792 : ReleaseSysCache(tup);
422 :
423 792 : 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 600 : errdomainconstraint(Oid datatypeOid, const char *conname)
432 : {
433 600 : errdatatype(datatypeOid);
434 600 : err_generic_string(PG_DIAG_CONSTRAINT_NAME, conname);
435 :
436 600 : return 0; /* return value does not matter */
437 : }
|