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 | |
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 | 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 |
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 |
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 |
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 * |
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 | |