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-2016, 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 *
domain_state_setup(Oid domainType,bool binary,MemoryContext mcxt)73 domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt)
74 {
75 DomainIOData *my_extra;
76 Oid baseType;
77
78 my_extra = (DomainIOData *) MemoryContextAlloc(mcxt, sizeof(DomainIOData));
79
80 /* Find out the base type */
81 my_extra->typtypmod = -1;
82 baseType = getBaseTypeAndTypmod(domainType, &my_extra->typtypmod);
83 if (baseType == domainType)
84 ereport(ERROR,
85 (errcode(ERRCODE_DATATYPE_MISMATCH),
86 errmsg("type %s is not a domain",
87 format_type_be(domainType))));
88
89 /* Look up underlying I/O function */
90 if (binary)
91 getTypeBinaryInputInfo(baseType,
92 &my_extra->typiofunc,
93 &my_extra->typioparam);
94 else
95 getTypeInputInfo(baseType,
96 &my_extra->typiofunc,
97 &my_extra->typioparam);
98 fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, mcxt);
99
100 /* Look up constraints for domain */
101 InitDomainConstraintRef(domainType, &my_extra->constraint_ref, mcxt);
102
103 /* We don't make an ExprContext until needed */
104 my_extra->econtext = NULL;
105 my_extra->mcxt = mcxt;
106
107 /* Mark cache valid */
108 my_extra->domain_type = domainType;
109
110 return my_extra;
111 }
112
113 /*
114 * domain_check_input - apply the cached checks.
115 *
116 * This is extremely similar to ExecEvalCoerceToDomain in execQual.c.
117 */
118 static void
domain_check_input(Datum value,bool isnull,DomainIOData * my_extra)119 domain_check_input(Datum value, bool isnull, DomainIOData *my_extra)
120 {
121 ExprContext *econtext = my_extra->econtext;
122 ListCell *l;
123
124 /* Make sure we have up-to-date constraints */
125 UpdateDomainConstraintRef(&my_extra->constraint_ref);
126
127 foreach(l, my_extra->constraint_ref.constraints)
128 {
129 DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
130
131 switch (con->constrainttype)
132 {
133 case DOM_CONSTRAINT_NOTNULL:
134 if (isnull)
135 ereport(ERROR,
136 (errcode(ERRCODE_NOT_NULL_VIOLATION),
137 errmsg("domain %s does not allow null values",
138 format_type_be(my_extra->domain_type)),
139 errdatatype(my_extra->domain_type)));
140 break;
141 case DOM_CONSTRAINT_CHECK:
142 {
143 Datum conResult;
144 bool conIsNull;
145
146 /* Make the econtext if we didn't already */
147 if (econtext == NULL)
148 {
149 MemoryContext oldcontext;
150
151 oldcontext = MemoryContextSwitchTo(my_extra->mcxt);
152 econtext = CreateStandaloneExprContext();
153 MemoryContextSwitchTo(oldcontext);
154 my_extra->econtext = econtext;
155 }
156
157 /*
158 * Set up value to be returned by CoerceToDomainValue
159 * nodes. Unlike ExecEvalCoerceToDomain, this econtext
160 * couldn't be shared with anything else, so no need to
161 * save and restore fields. But we do need to protect the
162 * passed-in value against being changed by called
163 * functions. (It couldn't be a R/W expanded object for
164 * most uses, but that seems possible for domain_check().)
165 */
166 econtext->domainValue_datum =
167 MakeExpandedObjectReadOnly(value, isnull,
168 my_extra->constraint_ref.tcache->typlen);
169 econtext->domainValue_isNull = isnull;
170
171 conResult = ExecEvalExprSwitchContext(con->check_expr,
172 econtext,
173 &conIsNull, NULL);
174
175 if (!conIsNull &&
176 !DatumGetBool(conResult))
177 ereport(ERROR,
178 (errcode(ERRCODE_CHECK_VIOLATION),
179 errmsg("value for domain %s violates check constraint \"%s\"",
180 format_type_be(my_extra->domain_type),
181 con->name),
182 errdomainconstraint(my_extra->domain_type,
183 con->name)));
184 break;
185 }
186 default:
187 elog(ERROR, "unrecognized constraint type: %d",
188 (int) con->constrainttype);
189 break;
190 }
191 }
192
193 /*
194 * Before exiting, call any shutdown callbacks and reset econtext's
195 * per-tuple memory. This avoids leaking non-memory resources, if
196 * anything in the expression(s) has any.
197 */
198 if (econtext)
199 ReScanExprContext(econtext);
200 }
201
202
203 /*
204 * domain_in - input routine for any domain type.
205 */
206 Datum
domain_in(PG_FUNCTION_ARGS)207 domain_in(PG_FUNCTION_ARGS)
208 {
209 char *string;
210 Oid domainType;
211 DomainIOData *my_extra;
212 Datum value;
213
214 /*
215 * Since domain_in is not strict, we have to check for null inputs. The
216 * typioparam argument should never be null in normal system usage, but it
217 * could be null in a manual invocation --- if so, just return null.
218 */
219 if (PG_ARGISNULL(0))
220 string = NULL;
221 else
222 string = PG_GETARG_CSTRING(0);
223 if (PG_ARGISNULL(1))
224 PG_RETURN_NULL();
225 domainType = PG_GETARG_OID(1);
226
227 /*
228 * We arrange to look up the needed info just once per series of calls,
229 * assuming the domain type doesn't change underneath us (which really
230 * shouldn't happen, but cope if it does).
231 */
232 my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra;
233 if (my_extra == NULL || my_extra->domain_type != domainType)
234 {
235 my_extra = domain_state_setup(domainType, false,
236 fcinfo->flinfo->fn_mcxt);
237 fcinfo->flinfo->fn_extra = (void *) my_extra;
238 }
239
240 /*
241 * Invoke the base type's typinput procedure to convert the data.
242 */
243 value = InputFunctionCall(&my_extra->proc,
244 string,
245 my_extra->typioparam,
246 my_extra->typtypmod);
247
248 /*
249 * Do the necessary checks to ensure it's a valid domain value.
250 */
251 domain_check_input(value, (string == NULL), my_extra);
252
253 if (string == NULL)
254 PG_RETURN_NULL();
255 else
256 PG_RETURN_DATUM(value);
257 }
258
259 /*
260 * domain_recv - binary input routine for any domain type.
261 */
262 Datum
domain_recv(PG_FUNCTION_ARGS)263 domain_recv(PG_FUNCTION_ARGS)
264 {
265 StringInfo buf;
266 Oid domainType;
267 DomainIOData *my_extra;
268 Datum value;
269
270 /*
271 * Since domain_recv is not strict, we have to check for null inputs. The
272 * typioparam argument should never be null in normal system usage, but it
273 * could be null in a manual invocation --- if so, just return null.
274 */
275 if (PG_ARGISNULL(0))
276 buf = NULL;
277 else
278 buf = (StringInfo) PG_GETARG_POINTER(0);
279 if (PG_ARGISNULL(1))
280 PG_RETURN_NULL();
281 domainType = PG_GETARG_OID(1);
282
283 /*
284 * We arrange to look up the needed info just once per series of calls,
285 * assuming the domain type doesn't change underneath us (which really
286 * shouldn't happen, but cope if it does).
287 */
288 my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra;
289 if (my_extra == NULL || my_extra->domain_type != domainType)
290 {
291 my_extra = domain_state_setup(domainType, true,
292 fcinfo->flinfo->fn_mcxt);
293 fcinfo->flinfo->fn_extra = (void *) my_extra;
294 }
295
296 /*
297 * Invoke the base type's typreceive procedure to convert the data.
298 */
299 value = ReceiveFunctionCall(&my_extra->proc,
300 buf,
301 my_extra->typioparam,
302 my_extra->typtypmod);
303
304 /*
305 * Do the necessary checks to ensure it's a valid domain value.
306 */
307 domain_check_input(value, (buf == NULL), my_extra);
308
309 if (buf == NULL)
310 PG_RETURN_NULL();
311 else
312 PG_RETURN_DATUM(value);
313 }
314
315 /*
316 * domain_check - check that a datum satisfies the constraints of a
317 * domain. extra and mcxt can be passed if they are available from,
318 * say, a FmgrInfo structure, or they can be NULL, in which case the
319 * setup is repeated for each call.
320 */
321 void
domain_check(Datum value,bool isnull,Oid domainType,void ** extra,MemoryContext mcxt)322 domain_check(Datum value, bool isnull, Oid domainType,
323 void **extra, MemoryContext mcxt)
324 {
325 DomainIOData *my_extra = NULL;
326
327 if (mcxt == NULL)
328 mcxt = CurrentMemoryContext;
329
330 /*
331 * We arrange to look up the needed info just once per series of calls,
332 * assuming the domain type doesn't change underneath us (which really
333 * shouldn't happen, but cope if it does).
334 */
335 if (extra)
336 my_extra = (DomainIOData *) *extra;
337 if (my_extra == NULL || my_extra->domain_type != domainType)
338 {
339 my_extra = domain_state_setup(domainType, true, mcxt);
340 if (extra)
341 *extra = (void *) my_extra;
342 }
343
344 /*
345 * Do the necessary checks to ensure it's a valid domain value.
346 */
347 domain_check_input(value, isnull, my_extra);
348 }
349
350 /*
351 * errdatatype --- stores schema_name and datatype_name of a datatype
352 * within the current errordata.
353 */
354 int
errdatatype(Oid datatypeOid)355 errdatatype(Oid datatypeOid)
356 {
357 HeapTuple tup;
358 Form_pg_type typtup;
359
360 tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(datatypeOid));
361 if (!HeapTupleIsValid(tup))
362 elog(ERROR, "cache lookup failed for type %u", datatypeOid);
363 typtup = (Form_pg_type) GETSTRUCT(tup);
364
365 err_generic_string(PG_DIAG_SCHEMA_NAME,
366 get_namespace_name(typtup->typnamespace));
367 err_generic_string(PG_DIAG_DATATYPE_NAME, NameStr(typtup->typname));
368
369 ReleaseSysCache(tup);
370
371 return 0; /* return value does not matter */
372 }
373
374 /*
375 * errdomainconstraint --- stores schema_name, datatype_name and
376 * constraint_name of a domain-related constraint within the current errordata.
377 */
378 int
errdomainconstraint(Oid datatypeOid,const char * conname)379 errdomainconstraint(Oid datatypeOid, const char *conname)
380 {
381 errdatatype(datatypeOid);
382 err_generic_string(PG_DIAG_CONSTRAINT_NAME, conname);
383
384 return 0; /* return value does not matter */
385 }
386