Age Owner Branch data TLA 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 *
3332 tgl@sss.pgh.pa.us 76 :CBC 1484 : domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt)
77 : : {
78 : : DomainIOData *my_extra;
79 : : TypeCacheEntry *typentry;
80 : : Oid baseType;
81 : :
82 : 1484 : 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 : : */
2362 91 : 1484 : typentry = lookup_type_cache(domainType, TYPECACHE_DOMAIN_BASE_INFO);
2774 92 [ - + ]: 1484 : if (typentry->typtype != TYPTYPE_DOMAIN)
6584 tgl@sss.pgh.pa.us 93 [ # # ]:UBC 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 */
2362 tgl@sss.pgh.pa.us 99 :CBC 1484 : baseType = typentry->domainBaseType;
100 : 1484 : my_extra->typtypmod = typentry->domainBaseTypmod;
101 : :
102 : : /* Look up underlying I/O function */
6584 103 [ + + ]: 1484 : if (binary)
104 : 1078 : getTypeBinaryInputInfo(baseType,
105 : : &my_extra->typiofunc,
106 : : &my_extra->typioparam);
107 : : else
108 : 406 : getTypeInputInfo(baseType,
109 : : &my_extra->typiofunc,
110 : : &my_extra->typioparam);
111 : 1484 : fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, mcxt);
112 : :
113 : : /* Look up constraints for domain */
2588 andres@anarazel.de 114 : 1484 : InitDomainConstraintRef(domainType, &my_extra->constraint_ref, mcxt, true);
115 : :
116 : : /* We don't make an ExprContext until needed */
6463 tgl@sss.pgh.pa.us 117 : 1484 : my_extra->econtext = NULL;
118 : 1484 : my_extra->mcxt = mcxt;
119 : :
120 : : /* Mark cache valid */
6584 121 : 1484 : my_extra->domain_type = domainType;
122 : :
3332 123 : 1484 : 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
490 138 : 169357 : domain_check_input(Datum value, bool isnull, DomainIOData *my_extra,
139 : : Node *escontext)
140 : : {
6463 141 : 169357 : ExprContext *econtext = my_extra->econtext;
142 : : ListCell *l;
143 : :
144 : : /* Make sure we have up-to-date constraints */
3332 145 : 169357 : UpdateDomainConstraintRef(&my_extra->constraint_ref);
146 : :
147 [ + + + + : 198823 : foreach(l, my_extra->constraint_ref.constraints)
+ + ]
148 : : {
6584 149 : 29589 : DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
150 : :
151 [ + + - ]: 29589 : switch (con->constrainttype)
152 : : {
153 : 111 : case DOM_CONSTRAINT_NOTNULL:
154 [ + + ]: 111 : if (isnull)
155 : : {
490 156 [ + + ]: 46 : 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)));
490 tgl@sss.pgh.pa.us 161 :GBC 24 : goto fail;
162 : : }
6584 tgl@sss.pgh.pa.us 163 :CBC 65 : break;
164 : 29478 : case DOM_CONSTRAINT_CHECK:
165 : : {
166 : : /* Make the econtext if we didn't already */
6463 167 [ + + ]: 29478 : if (econtext == NULL)
168 : : {
169 : : MemoryContext oldcontext;
170 : :
171 : 1122 : oldcontext = MemoryContextSwitchTo(my_extra->mcxt);
172 : 1122 : econtext = CreateStandaloneExprContext();
173 : 1122 : MemoryContextSwitchTo(oldcontext);
174 : 1122 : 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 : : */
2670 187 : 29478 : econtext->domainValue_datum =
188 [ + + + + ]: 29478 : MakeExpandedObjectReadOnly(value, isnull,
189 : : my_extra->constraint_ref.tcache->typlen);
6584 190 : 29478 : econtext->domainValue_isNull = isnull;
191 : :
2588 andres@anarazel.de 192 [ + + ]: 29478 : if (!ExecCheck(con->check_exprstate, econtext))
193 : : {
490 tgl@sss.pgh.pa.us 194 [ + + ]: 74 : 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 : 12 : goto fail;
202 : : }
6584 203 : 29401 : break;
204 : : }
6584 tgl@sss.pgh.pa.us 205 :UBC 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 : : */
490 tgl@sss.pgh.pa.us 217 :CBC 169258 : fail:
6463 218 [ + + ]: 169258 : if (econtext)
219 : 29413 : ReScanExprContext(econtext);
6584 220 : 169258 : }
221 : :
222 : :
223 : : /*
224 : : * domain_in - input routine for any domain type.
225 : : */
226 : : Datum
227 : 167842 : domain_in(PG_FUNCTION_ARGS)
228 : : {
229 : : char *string;
230 : : Oid domainType;
490 231 : 167842 : 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 : : */
6584 240 [ + + ]: 167842 : if (PG_ARGISNULL(0))
241 : 55 : string = NULL;
242 : : else
243 : 167787 : string = PG_GETARG_CSTRING(0);
244 [ - + ]: 167842 : if (PG_ARGISNULL(1))
6584 tgl@sss.pgh.pa.us 245 :UBC 0 : PG_RETURN_NULL();
6584 tgl@sss.pgh.pa.us 246 :CBC 167842 : 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 : 167842 : my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra;
3332 254 [ + + - + ]: 167842 : if (my_extra == NULL || my_extra->domain_type != domainType)
255 : : {
256 : 406 : my_extra = domain_state_setup(domainType, false,
257 : 406 : fcinfo->flinfo->fn_mcxt);
6584 258 : 406 : fcinfo->flinfo->fn_extra = (void *) my_extra;
259 : : }
260 : :
261 : : /*
262 : : * Invoke the base type's typinput procedure to convert the data.
263 : : */
490 264 [ + + ]: 167842 : if (!InputFunctionCallSafe(&my_extra->proc,
265 : : string,
266 : : my_extra->typioparam,
267 : : my_extra->typtypmod,
268 : : escontext,
269 : : &value))
270 : 9 : PG_RETURN_NULL();
271 : :
272 : : /*
273 : : * Do the necessary checks to ensure it's a valid domain value.
274 : : */
275 : 167827 : domain_check_input(value, (string == NULL), my_extra, escontext);
276 : :
6584 277 [ + + ]: 167807 : if (string == NULL)
278 : 72 : PG_RETURN_NULL();
279 : : else
280 : 167735 : PG_RETURN_DATUM(value);
281 : : }
282 : :
283 : : /*
284 : : * domain_recv - binary input routine for any domain type.
285 : : */
286 : : Datum
6584 tgl@sss.pgh.pa.us 287 :UBC 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;
3332 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);
6584 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 : : */
490 331 : 0 : domain_check_input(value, (buf == NULL), my_extra, NULL);
332 : :
6584 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
4093 tgl@sss.pgh.pa.us 346 :CBC 99 : domain_check(Datum value, bool isnull, Oid domainType,
347 : : void **extra, MemoryContext mcxt)
348 : : {
81 amitlan@postgresql.o 349 :GNC 99 : (void) domain_check_internal(value, isnull, domainType, extra, mcxt,
350 : : NULL);
351 : 65 : }
352 : :
353 : : /* Error-safe variant of domain_check(). */
354 : : bool
355 : 1431 : domain_check_safe(Datum value, bool isnull, Oid domainType,
356 : : void **extra, MemoryContext mcxt,
357 : : Node *escontext)
358 : : {
359 : 1431 : 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 : 1530 : domain_check_internal(Datum value, bool isnull, Oid domainType,
372 : : void **extra, MemoryContext mcxt,
373 : : Node *escontext)
374 : : {
5331 peter_e@gmx.net 375 :CBC 1530 : DomainIOData *my_extra = NULL;
376 : :
377 [ + + ]: 1530 : if (mcxt == NULL)
378 : 12 : 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 [ + + ]: 1530 : if (extra)
386 : 1518 : my_extra = (DomainIOData *) *extra;
3332 tgl@sss.pgh.pa.us 387 [ + + - + ]: 1530 : if (my_extra == NULL || my_extra->domain_type != domainType)
388 : : {
389 : 1078 : my_extra = domain_state_setup(domainType, true, mcxt);
5331 peter_e@gmx.net 390 [ + + ]: 1078 : if (extra)
391 : 1066 : *extra = (void *) my_extra;
392 : : }
393 : :
394 : : /*
395 : : * Do the necessary checks to ensure it's a valid domain value.
396 : : */
81 amitlan@postgresql.o 397 :GNC 1530 : domain_check_input(value, isnull, my_extra, escontext);
398 : :
399 [ + + + - : 1451 : return !SOFT_ERROR_OCCURRED(escontext);
+ + ]
5331 peter_e@gmx.net 400 :ECB (1415) : }
401 : :
402 : : /*
403 : : * errdatatype --- stores schema_name and datatype_name of a datatype
404 : : * within the current errordata.
405 : : */
406 : : int
4093 tgl@sss.pgh.pa.us 407 :CBC 369 : errdatatype(Oid datatypeOid)
408 : : {
409 : : HeapTuple tup;
410 : : Form_pg_type typtup;
411 : :
412 : 369 : tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(datatypeOid));
413 [ - + ]: 369 : if (!HeapTupleIsValid(tup))
4093 tgl@sss.pgh.pa.us 414 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for type %u", datatypeOid);
4093 tgl@sss.pgh.pa.us 415 :CBC 369 : typtup = (Form_pg_type) GETSTRUCT(tup);
416 : :
417 : 369 : err_generic_string(PG_DIAG_SCHEMA_NAME,
418 : 369 : get_namespace_name(typtup->typnamespace));
419 : 369 : err_generic_string(PG_DIAG_DATATYPE_NAME, NameStr(typtup->typname));
420 : :
421 : 369 : ReleaseSysCache(tup);
422 : :
423 : 369 : 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 : 279 : errdomainconstraint(Oid datatypeOid, const char *conname)
432 : : {
433 : 279 : errdatatype(datatypeOid);
434 : 279 : err_generic_string(PG_DIAG_CONSTRAINT_NAME, conname);
435 : :
436 : 279 : return 0; /* return value does not matter */
437 : : }
|