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