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