Line data Source code
1 : /*
2 : * brin_minmax.c
3 : * Implementation of Min/Max opclass for BRIN
4 : *
5 : * Portions Copyright (c) 1996-2017, 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
37 6604 : brin_minmax_opcinfo(PG_FUNCTION_ARGS)
38 : {
39 6604 : 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 6604 : result = palloc0(MAXALIGN(SizeofBrinOpcInfo(2)) +
48 : sizeof(MinmaxOpaque));
49 6604 : result->oi_nstored = 2;
50 6604 : result->oi_opaque = (MinmaxOpaque *)
51 6604 : MAXALIGN((char *) result + SizeofBrinOpcInfo(2));
52 6604 : result->oi_typcache[0] = result->oi_typcache[1] =
53 6604 : lookup_type_cache(typoid, 0);
54 :
55 6604 : 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
66 29045 : brin_minmax_add_value(PG_FUNCTION_ARGS)
67 : {
68 29045 : BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
69 29045 : BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
70 29045 : Datum newval = PG_GETARG_DATUM(2);
71 29045 : bool isnull = PG_GETARG_DATUM(3);
72 29045 : Oid colloid = PG_GET_COLLATION();
73 : FmgrInfo *cmpFn;
74 : Datum compar;
75 29045 : 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 29045 : if (isnull)
84 : {
85 720 : if (column->bv_hasnulls)
86 552 : PG_RETURN_BOOL(false);
87 :
88 168 : column->bv_hasnulls = true;
89 168 : PG_RETURN_BOOL(true);
90 : }
91 :
92 28325 : attno = column->bv_attno;
93 28325 : 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 28325 : if (column->bv_allnulls)
100 : {
101 2804 : column->bv_values[0] = datumCopy(newval, attr->attbyval, attr->attlen);
102 2804 : column->bv_values[1] = datumCopy(newval, attr->attbyval, attr->attlen);
103 2804 : column->bv_allnulls = false;
104 2804 : 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 25521 : cmpFn = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
113 : BTLessStrategyNumber);
114 25521 : compar = FunctionCall2Coll(cmpFn, colloid, newval, column->bv_values[0]);
115 25521 : if (DatumGetBool(compar))
116 : {
117 128 : if (!attr->attbyval)
118 105 : pfree(DatumGetPointer(column->bv_values[0]));
119 128 : column->bv_values[0] = datumCopy(newval, attr->attbyval, attr->attlen);
120 128 : updated = true;
121 : }
122 :
123 : /*
124 : * And now compare it to the existing maximum.
125 : */
126 25521 : cmpFn = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
127 : BTGreaterStrategyNumber);
128 25521 : compar = FunctionCall2Coll(cmpFn, colloid, newval, column->bv_values[1]);
129 25521 : if (DatumGetBool(compar))
130 : {
131 1455 : if (!attr->attbyval)
132 157 : pfree(DatumGetPointer(column->bv_values[1]));
133 1455 : column->bv_values[1] = datumCopy(newval, attr->attbyval, attr->attlen);
134 1455 : updated = true;
135 : }
136 :
137 25521 : 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
146 17700 : brin_minmax_consistent(PG_FUNCTION_ARGS)
147 : {
148 17700 : BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
149 17700 : BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
150 17700 : ScanKey key = (ScanKey) PG_GETARG_POINTER(2);
151 17700 : Oid colloid = PG_GET_COLLATION(),
152 : subtype;
153 : AttrNumber attno;
154 : Datum value;
155 : Datum matches;
156 : FmgrInfo *finfo;
157 :
158 17700 : Assert(key->sk_attno == column->bv_attno);
159 :
160 : /* handle IS NULL/IS NOT NULL tests */
161 17700 : if (key->sk_flags & SK_ISNULL)
162 : {
163 200 : if (key->sk_flags & SK_SEARCHNULL)
164 : {
165 100 : if (column->bv_allnulls || column->bv_hasnulls)
166 6 : PG_RETURN_BOOL(true);
167 94 : 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 100 : if (key->sk_flags & SK_SEARCHNOTNULL)
175 100 : 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 0 : PG_RETURN_BOOL(false);
182 : }
183 :
184 : /* if the range is all empty, it cannot possibly be consistent */
185 17500 : if (column->bv_allnulls)
186 0 : PG_RETURN_BOOL(false);
187 :
188 17500 : attno = key->sk_attno;
189 17500 : subtype = key->sk_subtype;
190 17500 : value = key->sk_argument;
191 17500 : switch (key->sk_strategy)
192 : {
193 : case BTLessStrategyNumber:
194 : case BTLessEqualStrategyNumber:
195 7000 : finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
196 7000 : key->sk_strategy);
197 7000 : matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
198 : value);
199 7000 : 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 3100 : finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
208 : BTLessEqualStrategyNumber);
209 3100 : matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
210 : value);
211 3100 : if (!DatumGetBool(matches))
212 1574 : break;
213 : /* max() >= scankey */
214 1526 : finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
215 : BTGreaterEqualStrategyNumber);
216 1526 : matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
217 : value);
218 1526 : break;
219 : case BTGreaterEqualStrategyNumber:
220 : case BTGreaterStrategyNumber:
221 7400 : finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
222 7400 : key->sk_strategy);
223 7400 : matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
224 : value);
225 7400 : break;
226 : default:
227 : /* shouldn't happen */
228 0 : elog(ERROR, "invalid strategy number %d", key->sk_strategy);
229 : matches = 0;
230 : break;
231 : }
232 :
233 17500 : 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
241 0 : brin_minmax_union(PG_FUNCTION_ARGS)
242 : {
243 0 : BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
244 0 : BrinValues *col_a = (BrinValues *) PG_GETARG_POINTER(1);
245 0 : BrinValues *col_b = (BrinValues *) PG_GETARG_POINTER(2);
246 0 : Oid colloid = PG_GET_COLLATION();
247 : AttrNumber attno;
248 : Form_pg_attribute attr;
249 : FmgrInfo *finfo;
250 : bool needsadj;
251 :
252 0 : Assert(col_a->bv_attno == col_b->bv_attno);
253 :
254 : /* Adjust "hasnulls" */
255 0 : if (!col_a->bv_hasnulls && col_b->bv_hasnulls)
256 0 : col_a->bv_hasnulls = true;
257 :
258 : /* If there are no values in B, there's nothing left to do */
259 0 : if (col_b->bv_allnulls)
260 0 : PG_RETURN_VOID();
261 :
262 0 : attno = col_a->bv_attno;
263 0 : 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 0 : if (col_a->bv_allnulls)
272 : {
273 0 : col_a->bv_allnulls = false;
274 0 : col_a->bv_values[0] = datumCopy(col_b->bv_values[0],
275 0 : attr->attbyval, attr->attlen);
276 0 : col_a->bv_values[1] = datumCopy(col_b->bv_values[1],
277 0 : attr->attbyval, attr->attlen);
278 0 : PG_RETURN_VOID();
279 : }
280 :
281 : /* Adjust minimum, if B's min is less than A's min */
282 0 : finfo = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
283 : BTLessStrategyNumber);
284 0 : needsadj = FunctionCall2Coll(finfo, colloid, col_b->bv_values[0],
285 0 : col_a->bv_values[0]);
286 0 : if (needsadj)
287 : {
288 0 : if (!attr->attbyval)
289 0 : pfree(DatumGetPointer(col_a->bv_values[0]));
290 0 : col_a->bv_values[0] = datumCopy(col_b->bv_values[0],
291 0 : attr->attbyval, attr->attlen);
292 : }
293 :
294 : /* Adjust maximum, if B's max is greater than A's max */
295 0 : finfo = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
296 : BTGreaterStrategyNumber);
297 0 : needsadj = FunctionCall2Coll(finfo, colloid, col_b->bv_values[1],
298 0 : col_a->bv_values[1]);
299 0 : if (needsadj)
300 : {
301 0 : if (!attr->attbyval)
302 0 : pfree(DatumGetPointer(col_a->bv_values[1]));
303 0 : col_a->bv_values[1] = datumCopy(col_b->bv_values[1],
304 0 : attr->attbyval, attr->attlen);
305 : }
306 :
307 0 : 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 *
317 70068 : minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype,
318 : uint16 strategynum)
319 : {
320 : MinmaxOpaque *opaque;
321 :
322 70068 : Assert(strategynum >= 1 &&
323 : strategynum <= BTMaxStrategyNumber);
324 :
325 70068 : 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 70068 : if (opaque->cached_subtype != subtype)
333 : {
334 : uint16 i;
335 :
336 1680 : for (i = 1; i <= BTMaxStrategyNumber; i++)
337 1400 : opaque->strategy_procinfos[i - 1].fn_oid = InvalidOid;
338 280 : opaque->cached_subtype = subtype;
339 : }
340 :
341 70068 : 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 416 : opfamily = bdesc->bd_index->rd_opfamily[attno - 1];
350 416 : attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
351 416 : tuple = SearchSysCache4(AMOPSTRATEGY, ObjectIdGetDatum(opfamily),
352 : ObjectIdGetDatum(attr->atttypid),
353 : ObjectIdGetDatum(subtype),
354 : Int16GetDatum(strategynum));
355 :
356 416 : if (!HeapTupleIsValid(tuple))
357 0 : elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
358 : strategynum, attr->atttypid, subtype, opfamily);
359 :
360 416 : oprid = DatumGetObjectId(SysCacheGetAttr(AMOPSTRATEGY, tuple,
361 : Anum_pg_amop_amopopr, &isNull));
362 416 : ReleaseSysCache(tuple);
363 416 : Assert(!isNull && RegProcedureIsValid(oprid));
364 :
365 832 : fmgr_info_cxt(get_opcode(oprid),
366 416 : &opaque->strategy_procinfos[strategynum - 1],
367 : bdesc->bd_context);
368 : }
369 :
370 70068 : return &opaque->strategy_procinfos[strategynum - 1];
371 : }
|