Age Owner 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-2023, 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 : */
47 : typedef 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 : */
72 : static DomainIOData *
2961 tgl 73 CBC 3038 : domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt)
74 : {
75 : DomainIOData *my_extra;
76 : TypeCacheEntry *typentry;
77 : Oid baseType;
78 :
79 3038 : 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 : */
1991 88 3038 : typentry = lookup_type_cache(domainType, TYPECACHE_DOMAIN_BASE_INFO);
2403 89 3038 : if (typentry->typtype != TYPTYPE_DOMAIN)
6213 tgl 90 UBC 0 : 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 */
1991 tgl 96 CBC 3038 : baseType = typentry->domainBaseType;
97 3038 : my_extra->typtypmod = typentry->domainBaseTypmod;
98 :
99 : /* Look up underlying I/O function */
6213 100 3038 : if (binary)
101 1039 : getTypeBinaryInputInfo(baseType,
102 : &my_extra->typiofunc,
103 : &my_extra->typioparam);
104 : else
105 1999 : getTypeInputInfo(baseType,
106 : &my_extra->typiofunc,
107 : &my_extra->typioparam);
108 3038 : fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, mcxt);
109 :
110 : /* Look up constraints for domain */
2217 andres 111 3038 : InitDomainConstraintRef(domainType, &my_extra->constraint_ref, mcxt, true);
112 :
113 : /* We don't make an ExprContext until needed */
6092 tgl 114 3038 : my_extra->econtext = NULL;
115 3038 : my_extra->mcxt = mcxt;
116 :
117 : /* Mark cache valid */
6213 118 3038 : my_extra->domain_type = domainType;
119 :
2961 120 3038 : 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 : *
130 : * If escontext points to an ErrorSaveContext, any failures are reported
131 : * there, otherwise they are ereport'ed. Note that we do not attempt to do
132 : * soft reporting of errors raised during execution of CHECK constraints.
133 : */
134 : static void
119 tgl 135 GNC 1372468 : domain_check_input(Datum value, bool isnull, DomainIOData *my_extra,
136 : Node *escontext)
137 : {
6092 tgl 138 GIC 1372468 : ExprContext *econtext = my_extra->econtext;
139 : ListCell *l;
6213 tgl 140 ECB :
141 : /* Make sure we have up-to-date constraints */
2961 tgl 142 GIC 1372468 : UpdateDomainConstraintRef(&my_extra->constraint_ref);
2961 tgl 143 ECB :
2961 tgl 144 GIC 1602434 : foreach(l, my_extra->constraint_ref.constraints)
145 : {
6213 146 230065 : DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
6213 tgl 147 ECB :
6213 tgl 148 GIC 230065 : switch (con->constrainttype)
6213 tgl 149 ECB : {
6213 tgl 150 GIC 84 : case DOM_CONSTRAINT_NOTNULL:
6213 tgl 151 CBC 84 : if (isnull)
152 : {
119 tgl 153 GNC 28 : errsave(escontext,
6213 tgl 154 ECB : (errcode(ERRCODE_NOT_NULL_VIOLATION),
155 : errmsg("domain %s does not allow null values",
3722 156 : format_type_be(my_extra->domain_type)),
157 : errdatatype(my_extra->domain_type)));
119 tgl 158 GNC 9 : goto fail;
159 : }
6213 tgl 160 GIC 56 : break;
6213 tgl 161 CBC 229981 : case DOM_CONSTRAINT_CHECK:
162 : {
163 : /* Make the econtext if we didn't already */
6092 tgl 164 GIC 229981 : if (econtext == NULL)
165 : {
6092 tgl 166 ECB : MemoryContext oldcontext;
167 :
6092 tgl 168 CBC 1370 : oldcontext = MemoryContextSwitchTo(my_extra->mcxt);
169 1370 : econtext = CreateStandaloneExprContext();
6092 tgl 170 GIC 1370 : MemoryContextSwitchTo(oldcontext);
171 1370 : my_extra->econtext = econtext;
6092 tgl 172 ECB : }
173 :
174 : /*
175 : * Set up value to be returned by CoerceToDomainValue
2217 andres 176 : * nodes. Unlike in the generic expression case, this
177 : * econtext couldn't be shared with anything else, so no
178 : * need to save and restore fields. But we do need to
179 : * protect the passed-in value against being changed by
180 : * called functions. (It couldn't be a R/W expanded
181 : * object for most uses, but that seems possible for
182 : * domain_check().)
183 : */
2299 tgl 184 GIC 229981 : econtext->domainValue_datum =
185 229981 : MakeExpandedObjectReadOnly(value, isnull,
186 : my_extra->constraint_ref.tcache->typlen);
6213 187 229981 : econtext->domainValue_isNull = isnull;
188 :
2217 andres 189 229981 : if (!ExecCheck(con->check_exprstate, econtext))
190 : {
119 tgl 191 GNC 68 : errsave(escontext,
192 : (errcode(ERRCODE_CHECK_VIOLATION),
6213 tgl 193 ECB : errmsg("value for domain %s violates check constraint \"%s\"",
194 : format_type_be(my_extra->domain_type),
195 : con->name),
3722 196 : errdomainconstraint(my_extra->domain_type,
197 : con->name)));
119 tgl 198 GNC 9 : goto fail;
199 : }
6213 tgl 200 CBC 229910 : break;
201 : }
6213 tgl 202 LBC 0 : default:
6213 tgl 203 UIC 0 : elog(ERROR, "unrecognized constraint type: %d",
204 : (int) con->constrainttype);
205 : break;
206 : }
207 : }
208 :
6092 tgl 209 ECB : /*
210 : * Before exiting, call any shutdown callbacks and reset econtext's
6031 bruce 211 : * per-tuple memory. This avoids leaking non-memory resources, if
212 : * anything in the expression(s) has any.
6092 tgl 213 EUB : */
119 tgl 214 GNC 1372378 : fail:
6092 tgl 215 GBC 1372378 : if (econtext)
6092 tgl 216 GIC 229919 : ReScanExprContext(econtext);
6213 217 1372378 : }
218 :
219 :
220 : /*
221 : * domain_in - input routine for any domain type.
222 : */
223 : Datum
224 1370998 : domain_in(PG_FUNCTION_ARGS)
225 : {
6213 tgl 226 ECB : char *string;
227 : Oid domainType;
119 tgl 228 GNC 1370998 : Node *escontext = fcinfo->context;
6213 tgl 229 ECB : DomainIOData *my_extra;
230 : Datum value;
231 :
232 : /*
233 : * Since domain_in is not strict, we have to check for null inputs. The
234 : * typioparam argument should never be null in normal system usage, but it
235 : * could be null in a manual invocation --- if so, just return null.
236 : */
6213 tgl 237 CBC 1370998 : if (PG_ARGISNULL(0))
6213 tgl 238 GIC 55 : string = NULL;
239 : else
240 1370943 : string = PG_GETARG_CSTRING(0);
6213 tgl 241 CBC 1370998 : if (PG_ARGISNULL(1))
6213 tgl 242 UIC 0 : PG_RETURN_NULL();
6213 tgl 243 GIC 1370998 : domainType = PG_GETARG_OID(1);
244 :
245 : /*
246 : * We arrange to look up the needed info just once per series of calls,
247 : * assuming the domain type doesn't change underneath us (which really
248 : * shouldn't happen, but cope if it does).
249 : */
6213 tgl 250 CBC 1370998 : my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra;
2961 251 1370998 : if (my_extra == NULL || my_extra->domain_type != domainType)
252 : {
253 1999 : my_extra = domain_state_setup(domainType, false,
254 1999 : fcinfo->flinfo->fn_mcxt);
6213 tgl 255 GBC 1999 : fcinfo->flinfo->fn_extra = (void *) my_extra;
6213 tgl 256 ECB : }
257 :
258 : /*
259 : * Invoke the base type's typinput procedure to convert the data.
260 : */
119 tgl 261 GNC 1370998 : if (!InputFunctionCallSafe(&my_extra->proc,
262 : string,
263 : my_extra->typioparam,
264 : my_extra->typtypmod,
265 : escontext,
266 : &value))
267 9 : PG_RETURN_NULL();
268 :
6213 tgl 269 ECB : /*
270 : * Do the necessary checks to ensure it's a valid domain value.
271 : */
119 tgl 272 GNC 1370983 : domain_check_input(value, (string == NULL), my_extra, escontext);
273 :
6213 tgl 274 GIC 1370963 : if (string == NULL)
275 69 : PG_RETURN_NULL();
276 : else
6213 tgl 277 CBC 1370894 : PG_RETURN_DATUM(value);
278 : }
279 :
280 : /*
281 : * domain_recv - binary input routine for any domain type.
282 : */
6213 tgl 283 ECB : Datum
6213 tgl 284 UIC 0 : domain_recv(PG_FUNCTION_ARGS)
285 : {
286 : StringInfo buf;
287 : Oid domainType;
6213 tgl 288 ECB : DomainIOData *my_extra;
289 : Datum value;
290 :
291 : /*
292 : * Since domain_recv is not strict, we have to check for null inputs. The
6031 bruce 293 : * typioparam argument should never be null in normal system usage, but it
294 : * could be null in a manual invocation --- if so, just return null.
295 : */
6213 tgl 296 UIC 0 : if (PG_ARGISNULL(0))
297 0 : buf = NULL;
298 : else
299 0 : buf = (StringInfo) PG_GETARG_POINTER(0);
6213 tgl 300 UBC 0 : if (PG_ARGISNULL(1))
6213 tgl 301 UIC 0 : PG_RETURN_NULL();
302 0 : domainType = PG_GETARG_OID(1);
303 :
304 : /*
305 : * We arrange to look up the needed info just once per series of calls,
306 : * assuming the domain type doesn't change underneath us (which really
307 : * shouldn't happen, but cope if it does).
308 : */
309 0 : my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra;
2961 310 0 : if (my_extra == NULL || my_extra->domain_type != domainType)
311 : {
2961 tgl 312 UBC 0 : my_extra = domain_state_setup(domainType, true,
313 0 : fcinfo->flinfo->fn_mcxt);
6213 tgl 314 UIC 0 : fcinfo->flinfo->fn_extra = (void *) my_extra;
6213 tgl 315 EUB : }
316 :
317 : /*
318 : * Invoke the base type's typreceive procedure to convert the data.
319 : */
6213 tgl 320 UIC 0 : value = ReceiveFunctionCall(&my_extra->proc,
321 : buf,
322 : my_extra->typioparam,
323 : my_extra->typtypmod);
324 :
6213 tgl 325 EUB : /*
326 : * Do the necessary checks to ensure it's a valid domain value.
327 : */
119 tgl 328 UNC 0 : domain_check_input(value, (buf == NULL), my_extra, NULL);
6213 tgl 329 EUB :
6213 tgl 330 UBC 0 : if (buf == NULL)
6213 tgl 331 UIC 0 : PG_RETURN_NULL();
332 : else
333 0 : PG_RETURN_DATUM(value);
334 : }
335 :
4960 peter_e 336 EUB : /*
337 : * domain_check - check that a datum satisfies the constraints of a
338 : * domain. extra and mcxt can be passed if they are available from,
339 : * say, a FmgrInfo structure, or they can be NULL, in which case the
340 : * setup is repeated for each call.
341 : */
342 : void
3722 tgl 343 GIC 1485 : domain_check(Datum value, bool isnull, Oid domainType,
3722 tgl 344 EUB : void **extra, MemoryContext mcxt)
345 : {
4960 peter_e 346 GBC 1485 : DomainIOData *my_extra = NULL;
4960 peter_e 347 EUB :
4960 peter_e 348 GIC 1485 : if (mcxt == NULL)
4960 peter_e 349 GBC 12 : mcxt = CurrentMemoryContext;
350 :
351 : /*
352 : * We arrange to look up the needed info just once per series of calls,
353 : * assuming the domain type doesn't change underneath us (which really
354 : * shouldn't happen, but cope if it does).
355 : */
4960 peter_e 356 GIC 1485 : if (extra)
357 1473 : my_extra = (DomainIOData *) *extra;
2961 tgl 358 1485 : if (my_extra == NULL || my_extra->domain_type != domainType)
4960 peter_e 359 ECB : {
2961 tgl 360 GIC 1039 : my_extra = domain_state_setup(domainType, true, mcxt);
4960 peter_e 361 1039 : if (extra)
4960 peter_e 362 CBC 1027 : *extra = (void *) my_extra;
363 : }
4960 peter_e 364 ECB :
365 : /*
366 : * Do the necessary checks to ensure it's a valid domain value.
367 : */
119 tgl 368 GNC 1485 : domain_check_input(value, isnull, my_extra, NULL);
4960 peter_e 369 GIC 1415 : }
370 :
371 : /*
3722 tgl 372 ECB : * errdatatype --- stores schema_name and datatype_name of a datatype
373 : * within the current errordata.
374 : */
375 : int
3722 tgl 376 CBC 324 : errdatatype(Oid datatypeOid)
3722 tgl 377 ECB : {
378 : HeapTuple tup;
379 : Form_pg_type typtup;
380 :
3722 tgl 381 GIC 324 : tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(datatypeOid));
382 324 : if (!HeapTupleIsValid(tup))
3722 tgl 383 UIC 0 : elog(ERROR, "cache lookup failed for type %u", datatypeOid);
3722 tgl 384 CBC 324 : typtup = (Form_pg_type) GETSTRUCT(tup);
3722 tgl 385 ECB :
3722 tgl 386 GIC 324 : err_generic_string(PG_DIAG_SCHEMA_NAME,
387 324 : get_namespace_name(typtup->typnamespace));
388 324 : err_generic_string(PG_DIAG_DATATYPE_NAME, NameStr(typtup->typname));
389 :
390 324 : ReleaseSysCache(tup);
391 :
3722 tgl 392 CBC 324 : return 0; /* return value does not matter */
393 : }
394 :
395 : /*
396 : * errdomainconstraint --- stores schema_name, datatype_name and
3722 tgl 397 ECB : * constraint_name of a domain-related constraint within the current errordata.
398 : */
3722 tgl 399 EUB : int
3722 tgl 400 CBC 249 : errdomainconstraint(Oid datatypeOid, const char *conname)
401 : {
402 249 : errdatatype(datatypeOid);
403 249 : err_generic_string(PG_DIAG_CONSTRAINT_NAME, conname);
3722 tgl 404 ECB :
3722 tgl 405 GIC 249 : return 0; /* return value does not matter */
3722 tgl 406 ECB : }
|