1 /*
2  * brin_minmax.c
3  *		Implementation of Min/Max opclass for BRIN
4  *
5  * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
6  * Portions Copyright (c) 1994, Regents of the University of California
7  *
8  * IDENTIFICATION
9  *	  src/backend/access/brin/brin_minmax.c
10  */
11 #include "postgres.h"
12 
13 #include "access/genam.h"
14 #include "access/brin_internal.h"
15 #include "access/brin_tuple.h"
16 #include "access/stratnum.h"
17 #include "catalog/pg_type.h"
18 #include "catalog/pg_amop.h"
19 #include "utils/builtins.h"
20 #include "utils/datum.h"
21 #include "utils/lsyscache.h"
22 #include "utils/rel.h"
23 #include "utils/syscache.h"
24 
25 
26 typedef struct MinmaxOpaque
27 {
28 	Oid			cached_subtype;
29 	FmgrInfo	strategy_procinfos[BTMaxStrategyNumber];
30 } MinmaxOpaque;
31 
32 static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno,
33 							 Oid subtype, uint16 strategynum);
34 
35 
36 Datum
brin_minmax_opcinfo(PG_FUNCTION_ARGS)37 brin_minmax_opcinfo(PG_FUNCTION_ARGS)
38 {
39 	Oid			typoid = PG_GETARG_OID(0);
40 	BrinOpcInfo *result;
41 
42 	/*
43 	 * opaque->strategy_procinfos is initialized lazily; here it is set to
44 	 * all-uninitialized by palloc0 which sets fn_oid to InvalidOid.
45 	 */
46 
47 	result = palloc0(MAXALIGN(SizeofBrinOpcInfo(2)) +
48 					 sizeof(MinmaxOpaque));
49 	result->oi_nstored = 2;
50 	result->oi_opaque = (MinmaxOpaque *)
51 		MAXALIGN((char *) result + SizeofBrinOpcInfo(2));
52 	result->oi_typcache[0] = result->oi_typcache[1] =
53 		lookup_type_cache(typoid, 0);
54 
55 	PG_RETURN_POINTER(result);
56 }
57 
58 /*
59  * Examine the given index tuple (which contains partial status of a certain
60  * page range) by comparing it to the given value that comes from another heap
61  * tuple.  If the new value is outside the min/max range specified by the
62  * existing tuple values, update the index tuple and return true.  Otherwise,
63  * return false and do not modify in this case.
64  */
65 Datum
brin_minmax_add_value(PG_FUNCTION_ARGS)66 brin_minmax_add_value(PG_FUNCTION_ARGS)
67 {
68 	BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
69 	BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
70 	Datum		newval = PG_GETARG_DATUM(2);
71 	bool		isnull = PG_GETARG_DATUM(3);
72 	Oid			colloid = PG_GET_COLLATION();
73 	FmgrInfo   *cmpFn;
74 	Datum		compar;
75 	bool		updated = false;
76 	Form_pg_attribute attr;
77 	AttrNumber	attno;
78 
79 	/*
80 	 * If the new value is null, we record that we saw it if it's the first
81 	 * one; otherwise, there's nothing to do.
82 	 */
83 	if (isnull)
84 	{
85 		if (column->bv_hasnulls)
86 			PG_RETURN_BOOL(false);
87 
88 		column->bv_hasnulls = true;
89 		PG_RETURN_BOOL(true);
90 	}
91 
92 	attno = column->bv_attno;
93 	attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
94 
95 	/*
96 	 * If the recorded value is null, store the new value (which we know to be
97 	 * not null) as both minimum and maximum, and we're done.
98 	 */
99 	if (column->bv_allnulls)
100 	{
101 		column->bv_values[0] = datumCopy(newval, attr->attbyval, attr->attlen);
102 		column->bv_values[1] = datumCopy(newval, attr->attbyval, attr->attlen);
103 		column->bv_allnulls = false;
104 		PG_RETURN_BOOL(true);
105 	}
106 
107 	/*
108 	 * Otherwise, need to compare the new value with the existing boundaries
109 	 * and update them accordingly.  First check if it's less than the
110 	 * existing minimum.
111 	 */
112 	cmpFn = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
113 										 BTLessStrategyNumber);
114 	compar = FunctionCall2Coll(cmpFn, colloid, newval, column->bv_values[0]);
115 	if (DatumGetBool(compar))
116 	{
117 		if (!attr->attbyval)
118 			pfree(DatumGetPointer(column->bv_values[0]));
119 		column->bv_values[0] = datumCopy(newval, attr->attbyval, attr->attlen);
120 		updated = true;
121 	}
122 
123 	/*
124 	 * And now compare it to the existing maximum.
125 	 */
126 	cmpFn = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
127 										 BTGreaterStrategyNumber);
128 	compar = FunctionCall2Coll(cmpFn, colloid, newval, column->bv_values[1]);
129 	if (DatumGetBool(compar))
130 	{
131 		if (!attr->attbyval)
132 			pfree(DatumGetPointer(column->bv_values[1]));
133 		column->bv_values[1] = datumCopy(newval, attr->attbyval, attr->attlen);
134 		updated = true;
135 	}
136 
137 	PG_RETURN_BOOL(updated);
138 }
139 
140 /*
141  * Given an index tuple corresponding to a certain page range and a scan key,
142  * return whether the scan key is consistent with the index tuple's min/max
143  * values.  Return true if so, false otherwise.
144  */
145 Datum
brin_minmax_consistent(PG_FUNCTION_ARGS)146 brin_minmax_consistent(PG_FUNCTION_ARGS)
147 {
148 	BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
149 	BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
150 	ScanKey		key = (ScanKey) PG_GETARG_POINTER(2);
151 	Oid			colloid = PG_GET_COLLATION(),
152 				subtype;
153 	AttrNumber	attno;
154 	Datum		value;
155 	Datum		matches;
156 	FmgrInfo   *finfo;
157 
158 	Assert(key->sk_attno == column->bv_attno);
159 
160 	/* handle IS NULL/IS NOT NULL tests */
161 	if (key->sk_flags & SK_ISNULL)
162 	{
163 		if (key->sk_flags & SK_SEARCHNULL)
164 		{
165 			if (column->bv_allnulls || column->bv_hasnulls)
166 				PG_RETURN_BOOL(true);
167 			PG_RETURN_BOOL(false);
168 		}
169 
170 		/*
171 		 * For IS NOT NULL, we can only skip ranges that are known to have
172 		 * only nulls.
173 		 */
174 		if (key->sk_flags & SK_SEARCHNOTNULL)
175 			PG_RETURN_BOOL(!column->bv_allnulls);
176 
177 		/*
178 		 * Neither IS NULL nor IS NOT NULL was used; assume all indexable
179 		 * operators are strict and return false.
180 		 */
181 		PG_RETURN_BOOL(false);
182 	}
183 
184 	/* if the range is all empty, it cannot possibly be consistent */
185 	if (column->bv_allnulls)
186 		PG_RETURN_BOOL(false);
187 
188 	attno = key->sk_attno;
189 	subtype = key->sk_subtype;
190 	value = key->sk_argument;
191 	switch (key->sk_strategy)
192 	{
193 		case BTLessStrategyNumber:
194 		case BTLessEqualStrategyNumber:
195 			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
196 												 key->sk_strategy);
197 			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
198 										value);
199 			break;
200 		case BTEqualStrategyNumber:
201 
202 			/*
203 			 * In the equality case (WHERE col = someval), we want to return
204 			 * the current page range if the minimum value in the range <=
205 			 * scan key, and the maximum value >= scan key.
206 			 */
207 			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
208 												 BTLessEqualStrategyNumber);
209 			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
210 										value);
211 			if (!DatumGetBool(matches))
212 				break;
213 			/* max() >= scankey */
214 			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
215 												 BTGreaterEqualStrategyNumber);
216 			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
217 										value);
218 			break;
219 		case BTGreaterEqualStrategyNumber:
220 		case BTGreaterStrategyNumber:
221 			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
222 												 key->sk_strategy);
223 			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
224 										value);
225 			break;
226 		default:
227 			/* shouldn't happen */
228 			elog(ERROR, "invalid strategy number %d", key->sk_strategy);
229 			matches = 0;
230 			break;
231 	}
232 
233 	PG_RETURN_DATUM(matches);
234 }
235 
236 /*
237  * Given two BrinValues, update the first of them as a union of the summary
238  * values contained in both.  The second one is untouched.
239  */
240 Datum
brin_minmax_union(PG_FUNCTION_ARGS)241 brin_minmax_union(PG_FUNCTION_ARGS)
242 {
243 	BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
244 	BrinValues *col_a = (BrinValues *) PG_GETARG_POINTER(1);
245 	BrinValues *col_b = (BrinValues *) PG_GETARG_POINTER(2);
246 	Oid			colloid = PG_GET_COLLATION();
247 	AttrNumber	attno;
248 	Form_pg_attribute attr;
249 	FmgrInfo   *finfo;
250 	bool		needsadj;
251 
252 	Assert(col_a->bv_attno == col_b->bv_attno);
253 
254 	/* Adjust "hasnulls" */
255 	if (!col_a->bv_hasnulls && col_b->bv_hasnulls)
256 		col_a->bv_hasnulls = true;
257 
258 	/* If there are no values in B, there's nothing left to do */
259 	if (col_b->bv_allnulls)
260 		PG_RETURN_VOID();
261 
262 	attno = col_a->bv_attno;
263 	attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
264 
265 	/*
266 	 * Adjust "allnulls".  If A doesn't have values, just copy the values from
267 	 * B into A, and we're done.  We cannot run the operators in this case,
268 	 * because values in A might contain garbage.  Note we already established
269 	 * that B contains values.
270 	 */
271 	if (col_a->bv_allnulls)
272 	{
273 		col_a->bv_allnulls = false;
274 		col_a->bv_values[0] = datumCopy(col_b->bv_values[0],
275 										attr->attbyval, attr->attlen);
276 		col_a->bv_values[1] = datumCopy(col_b->bv_values[1],
277 										attr->attbyval, attr->attlen);
278 		PG_RETURN_VOID();
279 	}
280 
281 	/* Adjust minimum, if B's min is less than A's min */
282 	finfo = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
283 										 BTLessStrategyNumber);
284 	needsadj = FunctionCall2Coll(finfo, colloid, col_b->bv_values[0],
285 								 col_a->bv_values[0]);
286 	if (needsadj)
287 	{
288 		if (!attr->attbyval)
289 			pfree(DatumGetPointer(col_a->bv_values[0]));
290 		col_a->bv_values[0] = datumCopy(col_b->bv_values[0],
291 										attr->attbyval, attr->attlen);
292 	}
293 
294 	/* Adjust maximum, if B's max is greater than A's max */
295 	finfo = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
296 										 BTGreaterStrategyNumber);
297 	needsadj = FunctionCall2Coll(finfo, colloid, col_b->bv_values[1],
298 								 col_a->bv_values[1]);
299 	if (needsadj)
300 	{
301 		if (!attr->attbyval)
302 			pfree(DatumGetPointer(col_a->bv_values[1]));
303 		col_a->bv_values[1] = datumCopy(col_b->bv_values[1],
304 										attr->attbyval, attr->attlen);
305 	}
306 
307 	PG_RETURN_VOID();
308 }
309 
310 /*
311  * Cache and return the procedure for the given strategy.
312  *
313  * Note: this function mirrors inclusion_get_strategy_procinfo; see notes
314  * there.  If changes are made here, see that function too.
315  */
316 static FmgrInfo *
minmax_get_strategy_procinfo(BrinDesc * bdesc,uint16 attno,Oid subtype,uint16 strategynum)317 minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype,
318 							 uint16 strategynum)
319 {
320 	MinmaxOpaque *opaque;
321 
322 	Assert(strategynum >= 1 &&
323 		   strategynum <= BTMaxStrategyNumber);
324 
325 	opaque = (MinmaxOpaque *) bdesc->bd_info[attno - 1]->oi_opaque;
326 
327 	/*
328 	 * We cache the procedures for the previous subtype in the opaque struct,
329 	 * to avoid repetitive syscache lookups.  If the subtype changed,
330 	 * invalidate all the cached entries.
331 	 */
332 	if (opaque->cached_subtype != subtype)
333 	{
334 		uint16		i;
335 
336 		for (i = 1; i <= BTMaxStrategyNumber; i++)
337 			opaque->strategy_procinfos[i - 1].fn_oid = InvalidOid;
338 		opaque->cached_subtype = subtype;
339 	}
340 
341 	if (opaque->strategy_procinfos[strategynum - 1].fn_oid == InvalidOid)
342 	{
343 		Form_pg_attribute attr;
344 		HeapTuple	tuple;
345 		Oid			opfamily,
346 					oprid;
347 		bool		isNull;
348 
349 		opfamily = bdesc->bd_index->rd_opfamily[attno - 1];
350 		attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
351 		tuple = SearchSysCache4(AMOPSTRATEGY, ObjectIdGetDatum(opfamily),
352 								ObjectIdGetDatum(attr->atttypid),
353 								ObjectIdGetDatum(subtype),
354 								Int16GetDatum(strategynum));
355 
356 		if (!HeapTupleIsValid(tuple))
357 			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
358 				 strategynum, attr->atttypid, subtype, opfamily);
359 
360 		oprid = DatumGetObjectId(SysCacheGetAttr(AMOPSTRATEGY, tuple,
361 												 Anum_pg_amop_amopopr, &isNull));
362 		ReleaseSysCache(tuple);
363 		Assert(!isNull && RegProcedureIsValid(oprid));
364 
365 		fmgr_info_cxt(get_opcode(oprid),
366 					  &opaque->strategy_procinfos[strategynum - 1],
367 					  bdesc->bd_context);
368 	}
369 
370 	return &opaque->strategy_procinfos[strategynum - 1];
371 }
372