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