1/*
2 * brin_minmax.c
3 * Implementation of Min/Max opclass for BRIN
4 *
5 * Portions Copyright (c) 1996-2019, 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
26typedef struct MinmaxOpaque
27{
28 Oid cached_subtype;
29 FmgrInfo strategy_procinfos[BTMaxStrategyNumber];
30} MinmaxOpaque;
31
32static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno,
33 Oid subtype, uint16 strategynum);
34
35
36Datum
37brin_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 */
65Datum
66brin_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 */
145Datum
146brin_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 */
240Datum
241brin_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 */
316static FmgrInfo *
317minmax_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