1 /*
2  * brin_minmax.c
3  *		Implementation of Min/Max opclass for BRIN
4  *
5  * Portions Copyright (c) 1996-2021, 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/brin_internal.h"
14 #include "access/brin_tuple.h"
15 #include "access/genam.h"
16 #include "access/stratnum.h"
17 #include "catalog/pg_amop.h"
18 #include "catalog/pg_type.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 typedef struct MinmaxOpaque
26 {
27 	Oid			cached_subtype;
28 	FmgrInfo	strategy_procinfos[BTMaxStrategyNumber];
29 } MinmaxOpaque;
30 
31 static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno,
32 											  Oid subtype, uint16 strategynum);
33 
34 
35 Datum
brin_minmax_opcinfo(PG_FUNCTION_ARGS)36 brin_minmax_opcinfo(PG_FUNCTION_ARGS)
37 {
38 	Oid			typoid = PG_GETARG_OID(0);
39 	BrinOpcInfo *result;
40 
41 	/*
42 	 * opaque->strategy_procinfos is initialized lazily; here it is set to
43 	 * all-uninitialized by palloc0 which sets fn_oid to InvalidOid.
44 	 */
45 
46 	result = palloc0(MAXALIGN(SizeofBrinOpcInfo(2)) +
47 					 sizeof(MinmaxOpaque));
48 	result->oi_nstored = 2;
49 	result->oi_regular_nulls = true;
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_USED_FOR_ASSERTS_ONLY = 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 	Assert(!isnull);
80 
81 	attno = column->bv_attno;
82 	attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
83 
84 	/*
85 	 * If the recorded value is null, store the new value (which we know to be
86 	 * not null) as both minimum and maximum, and we're done.
87 	 */
88 	if (column->bv_allnulls)
89 	{
90 		column->bv_values[0] = datumCopy(newval, attr->attbyval, attr->attlen);
91 		column->bv_values[1] = datumCopy(newval, attr->attbyval, attr->attlen);
92 		column->bv_allnulls = false;
93 		PG_RETURN_BOOL(true);
94 	}
95 
96 	/*
97 	 * Otherwise, need to compare the new value with the existing boundaries
98 	 * and update them accordingly.  First check if it's less than the
99 	 * existing minimum.
100 	 */
101 	cmpFn = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
102 										 BTLessStrategyNumber);
103 	compar = FunctionCall2Coll(cmpFn, colloid, newval, column->bv_values[0]);
104 	if (DatumGetBool(compar))
105 	{
106 		if (!attr->attbyval)
107 			pfree(DatumGetPointer(column->bv_values[0]));
108 		column->bv_values[0] = datumCopy(newval, attr->attbyval, attr->attlen);
109 		updated = true;
110 	}
111 
112 	/*
113 	 * And now compare it to the existing maximum.
114 	 */
115 	cmpFn = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
116 										 BTGreaterStrategyNumber);
117 	compar = FunctionCall2Coll(cmpFn, colloid, newval, column->bv_values[1]);
118 	if (DatumGetBool(compar))
119 	{
120 		if (!attr->attbyval)
121 			pfree(DatumGetPointer(column->bv_values[1]));
122 		column->bv_values[1] = datumCopy(newval, attr->attbyval, attr->attlen);
123 		updated = true;
124 	}
125 
126 	PG_RETURN_BOOL(updated);
127 }
128 
129 /*
130  * Given an index tuple corresponding to a certain page range and a scan key,
131  * return whether the scan key is consistent with the index tuple's min/max
132  * values.  Return true if so, false otherwise.
133  *
134  * We're no longer dealing with NULL keys in the consistent function, that is
135  * now handled by the AM code. That means we should not get any all-NULL ranges
136  * either, because those can't be consistent with regular (not [IS] NULL) keys.
137  */
138 Datum
brin_minmax_consistent(PG_FUNCTION_ARGS)139 brin_minmax_consistent(PG_FUNCTION_ARGS)
140 {
141 	BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
142 	BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
143 	ScanKey		key = (ScanKey) PG_GETARG_POINTER(2);
144 	Oid			colloid = PG_GET_COLLATION(),
145 				subtype;
146 	AttrNumber	attno;
147 	Datum		value;
148 	Datum		matches;
149 	FmgrInfo   *finfo;
150 
151 	/* This opclass uses the old signature with only three arguments. */
152 	Assert(PG_NARGS() == 3);
153 
154 	/* Should not be dealing with all-NULL ranges. */
155 	Assert(!column->bv_allnulls);
156 
157 	attno = key->sk_attno;
158 	subtype = key->sk_subtype;
159 	value = key->sk_argument;
160 	switch (key->sk_strategy)
161 	{
162 		case BTLessStrategyNumber:
163 		case BTLessEqualStrategyNumber:
164 			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
165 												 key->sk_strategy);
166 			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
167 										value);
168 			break;
169 		case BTEqualStrategyNumber:
170 
171 			/*
172 			 * In the equality case (WHERE col = someval), we want to return
173 			 * the current page range if the minimum value in the range <=
174 			 * scan key, and the maximum value >= scan key.
175 			 */
176 			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
177 												 BTLessEqualStrategyNumber);
178 			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
179 										value);
180 			if (!DatumGetBool(matches))
181 				break;
182 			/* max() >= scankey */
183 			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
184 												 BTGreaterEqualStrategyNumber);
185 			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
186 										value);
187 			break;
188 		case BTGreaterEqualStrategyNumber:
189 		case BTGreaterStrategyNumber:
190 			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
191 												 key->sk_strategy);
192 			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
193 										value);
194 			break;
195 		default:
196 			/* shouldn't happen */
197 			elog(ERROR, "invalid strategy number %d", key->sk_strategy);
198 			matches = 0;
199 			break;
200 	}
201 
202 	PG_RETURN_DATUM(matches);
203 }
204 
205 /*
206  * Given two BrinValues, update the first of them as a union of the summary
207  * values contained in both.  The second one is untouched.
208  */
209 Datum
brin_minmax_union(PG_FUNCTION_ARGS)210 brin_minmax_union(PG_FUNCTION_ARGS)
211 {
212 	BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
213 	BrinValues *col_a = (BrinValues *) PG_GETARG_POINTER(1);
214 	BrinValues *col_b = (BrinValues *) PG_GETARG_POINTER(2);
215 	Oid			colloid = PG_GET_COLLATION();
216 	AttrNumber	attno;
217 	Form_pg_attribute attr;
218 	FmgrInfo   *finfo;
219 	bool		needsadj;
220 
221 	Assert(col_a->bv_attno == col_b->bv_attno);
222 	Assert(!col_a->bv_allnulls && !col_b->bv_allnulls);
223 
224 	attno = col_a->bv_attno;
225 	attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
226 
227 	/* Adjust minimum, if B's min is less than A's min */
228 	finfo = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
229 										 BTLessStrategyNumber);
230 	needsadj = FunctionCall2Coll(finfo, colloid, col_b->bv_values[0],
231 								 col_a->bv_values[0]);
232 	if (needsadj)
233 	{
234 		if (!attr->attbyval)
235 			pfree(DatumGetPointer(col_a->bv_values[0]));
236 		col_a->bv_values[0] = datumCopy(col_b->bv_values[0],
237 										attr->attbyval, attr->attlen);
238 	}
239 
240 	/* Adjust maximum, if B's max is greater than A's max */
241 	finfo = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
242 										 BTGreaterStrategyNumber);
243 	needsadj = FunctionCall2Coll(finfo, colloid, col_b->bv_values[1],
244 								 col_a->bv_values[1]);
245 	if (needsadj)
246 	{
247 		if (!attr->attbyval)
248 			pfree(DatumGetPointer(col_a->bv_values[1]));
249 		col_a->bv_values[1] = datumCopy(col_b->bv_values[1],
250 										attr->attbyval, attr->attlen);
251 	}
252 
253 	PG_RETURN_VOID();
254 }
255 
256 /*
257  * Cache and return the procedure for the given strategy.
258  *
259  * Note: this function mirrors inclusion_get_strategy_procinfo; see notes
260  * there.  If changes are made here, see that function too.
261  */
262 static FmgrInfo *
minmax_get_strategy_procinfo(BrinDesc * bdesc,uint16 attno,Oid subtype,uint16 strategynum)263 minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype,
264 							 uint16 strategynum)
265 {
266 	MinmaxOpaque *opaque;
267 
268 	Assert(strategynum >= 1 &&
269 		   strategynum <= BTMaxStrategyNumber);
270 
271 	opaque = (MinmaxOpaque *) bdesc->bd_info[attno - 1]->oi_opaque;
272 
273 	/*
274 	 * We cache the procedures for the previous subtype in the opaque struct,
275 	 * to avoid repetitive syscache lookups.  If the subtype changed,
276 	 * invalidate all the cached entries.
277 	 */
278 	if (opaque->cached_subtype != subtype)
279 	{
280 		uint16		i;
281 
282 		for (i = 1; i <= BTMaxStrategyNumber; i++)
283 			opaque->strategy_procinfos[i - 1].fn_oid = InvalidOid;
284 		opaque->cached_subtype = subtype;
285 	}
286 
287 	if (opaque->strategy_procinfos[strategynum - 1].fn_oid == InvalidOid)
288 	{
289 		Form_pg_attribute attr;
290 		HeapTuple	tuple;
291 		Oid			opfamily,
292 					oprid;
293 		bool		isNull;
294 
295 		opfamily = bdesc->bd_index->rd_opfamily[attno - 1];
296 		attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
297 		tuple = SearchSysCache4(AMOPSTRATEGY, ObjectIdGetDatum(opfamily),
298 								ObjectIdGetDatum(attr->atttypid),
299 								ObjectIdGetDatum(subtype),
300 								Int16GetDatum(strategynum));
301 
302 		if (!HeapTupleIsValid(tuple))
303 			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
304 				 strategynum, attr->atttypid, subtype, opfamily);
305 
306 		oprid = DatumGetObjectId(SysCacheGetAttr(AMOPSTRATEGY, tuple,
307 												 Anum_pg_amop_amopopr, &isNull));
308 		ReleaseSysCache(tuple);
309 		Assert(!isNull && RegProcedureIsValid(oprid));
310 
311 		fmgr_info_cxt(get_opcode(oprid),
312 					  &opaque->strategy_procinfos[strategynum - 1],
313 					  bdesc->bd_context);
314 	}
315 
316 	return &opaque->strategy_procinfos[strategynum - 1];
317 }
318