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