Age Owner Branch data TLA Line data Source code
1 : : /*
2 : : * brin_minmax.c
3 : : * Implementation of Min/Max opclass for BRIN
4 : : *
5 : : * Portions Copyright (c) 1996-2024, 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/stratnum.h"
16 : : #include "catalog/pg_amop.h"
17 : : #include "utils/datum.h"
18 : : #include "utils/fmgrprotos.h"
19 : : #include "utils/lsyscache.h"
20 : : #include "utils/rel.h"
21 : : #include "utils/syscache.h"
22 : :
23 : : typedef struct MinmaxOpaque
24 : : {
25 : : Oid cached_subtype;
26 : : FmgrInfo strategy_procinfos[BTMaxStrategyNumber];
27 : : } MinmaxOpaque;
28 : :
29 : : static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno,
30 : : Oid subtype, uint16 strategynum);
31 : :
32 : :
33 : : Datum
3421 tgl@sss.pgh.pa.us 34 :CBC 20519 : brin_minmax_opcinfo(PG_FUNCTION_ARGS)
35 : : {
3446 alvherre@alvh.no-ip. 36 : 20519 : Oid typoid = PG_GETARG_OID(0);
37 : : BrinOpcInfo *result;
38 : :
39 : : /*
40 : : * opaque->strategy_procinfos is initialized lazily; here it is set to
41 : : * all-uninitialized by palloc0 which sets fn_oid to InvalidOid.
42 : : */
43 : :
44 : 20519 : result = palloc0(MAXALIGN(SizeofBrinOpcInfo(2)) +
45 : : sizeof(MinmaxOpaque));
46 : 20519 : result->oi_nstored = 2;
1118 tomas.vondra@postgre 47 : 20519 : result->oi_regular_nulls = true;
3446 alvherre@alvh.no-ip. 48 : 20519 : result->oi_opaque = (MinmaxOpaque *)
49 : 20519 : MAXALIGN((char *) result + SizeofBrinOpcInfo(2));
3265 50 : 20519 : result->oi_typcache[0] = result->oi_typcache[1] =
51 : 20519 : lookup_type_cache(typoid, 0);
52 : :
3446 53 : 20519 : PG_RETURN_POINTER(result);
54 : : }
55 : :
56 : : /*
57 : : * Examine the given index tuple (which contains partial status of a certain
58 : : * page range) by comparing it to the given value that comes from another heap
59 : : * tuple. If the new value is outside the min/max range specified by the
60 : : * existing tuple values, update the index tuple and return true. Otherwise,
61 : : * return false and do not modify in this case.
62 : : */
63 : : Datum
3421 tgl@sss.pgh.pa.us 64 : 415075 : brin_minmax_add_value(PG_FUNCTION_ARGS)
65 : : {
3446 alvherre@alvh.no-ip. 66 : 415075 : BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
67 : 415075 : BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
68 : 415075 : Datum newval = PG_GETARG_DATUM(2);
1118 tomas.vondra@postgre 69 : 415075 : bool isnull PG_USED_FOR_ASSERTS_ONLY = PG_GETARG_DATUM(3);
3446 alvherre@alvh.no-ip. 70 : 415075 : Oid colloid = PG_GET_COLLATION();
71 : : FmgrInfo *cmpFn;
72 : : Datum compar;
73 : 415075 : bool updated = false;
74 : : Form_pg_attribute attr;
75 : : AttrNumber attno;
76 : :
1118 tomas.vondra@postgre 77 [ - + ]: 415075 : Assert(!isnull);
78 : :
3446 alvherre@alvh.no-ip. 79 : 415075 : attno = column->bv_attno;
2429 andres@anarazel.de 80 : 415075 : attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
81 : :
82 : : /*
83 : : * If the recorded value is null, store the new value (which we know to be
84 : : * not null) as both minimum and maximum, and we're done.
85 : : */
3446 alvherre@alvh.no-ip. 86 [ + + ]: 415075 : if (column->bv_allnulls)
87 : : {
88 : 10421 : column->bv_values[0] = datumCopy(newval, attr->attbyval, attr->attlen);
89 : 10421 : column->bv_values[1] = datumCopy(newval, attr->attbyval, attr->attlen);
90 : 10421 : column->bv_allnulls = false;
91 : 10421 : PG_RETURN_BOOL(true);
92 : : }
93 : :
94 : : /*
95 : : * Otherwise, need to compare the new value with the existing boundaries
96 : : * and update them accordingly. First check if it's less than the
97 : : * existing minimum.
98 : : */
3265 99 : 404654 : cmpFn = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
100 : : BTLessStrategyNumber);
3446 101 : 404654 : compar = FunctionCall2Coll(cmpFn, colloid, newval, column->bv_values[0]);
102 [ + + ]: 404654 : if (DatumGetBool(compar))
103 : : {
104 [ + + ]: 776 : if (!attr->attbyval)
105 : 566 : pfree(DatumGetPointer(column->bv_values[0]));
106 : 776 : column->bv_values[0] = datumCopy(newval, attr->attbyval, attr->attlen);
107 : 776 : updated = true;
108 : : }
109 : :
110 : : /*
111 : : * And now compare it to the existing maximum.
112 : : */
3265 113 : 404654 : cmpFn = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
114 : : BTGreaterStrategyNumber);
3446 115 : 404654 : compar = FunctionCall2Coll(cmpFn, colloid, newval, column->bv_values[1]);
116 [ + + ]: 404654 : if (DatumGetBool(compar))
117 : : {
118 [ + + ]: 167020 : if (!attr->attbyval)
119 : 435 : pfree(DatumGetPointer(column->bv_values[1]));
120 : 167020 : column->bv_values[1] = datumCopy(newval, attr->attbyval, attr->attlen);
121 : 167020 : updated = true;
122 : : }
123 : :
124 : 404654 : PG_RETURN_BOOL(updated);
125 : : }
126 : :
127 : : /*
128 : : * Given an index tuple corresponding to a certain page range and a scan key,
129 : : * return whether the scan key is consistent with the index tuple's min/max
130 : : * values. Return true if so, false otherwise.
131 : : *
132 : : * We're no longer dealing with NULL keys in the consistent function, that is
133 : : * now handled by the AM code. That means we should not get any all-NULL ranges
134 : : * either, because those can't be consistent with regular (not [IS] NULL) keys.
135 : : */
136 : : Datum
3421 tgl@sss.pgh.pa.us 137 : 52566 : brin_minmax_consistent(PG_FUNCTION_ARGS)
138 : : {
3446 alvherre@alvh.no-ip. 139 : 52566 : BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
140 : 52566 : BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
1115 tomas.vondra@postgre 141 : 52566 : ScanKey key = (ScanKey) PG_GETARG_POINTER(2);
142 : 52566 : Oid colloid = PG_GET_COLLATION(),
143 : : subtype;
144 : : AttrNumber attno;
145 : : Datum value;
146 : : Datum matches;
147 : : FmgrInfo *finfo;
148 : :
149 : : /* This opclass uses the old signature with only three arguments. */
150 [ - + ]: 52566 : Assert(PG_NARGS() == 3);
151 : :
152 : : /* Should not be dealing with all-NULL ranges. */
153 [ - + ]: 52566 : Assert(!column->bv_allnulls);
154 : :
155 : 52566 : attno = key->sk_attno;
156 : 52566 : subtype = key->sk_subtype;
157 : 52566 : value = key->sk_argument;
3446 alvherre@alvh.no-ip. 158 [ + + + - ]: 52566 : switch (key->sk_strategy)
159 : : {
160 : 21003 : case BTLessStrategyNumber:
161 : : case BTLessEqualStrategyNumber:
3265 162 : 21003 : finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
163 : 21003 : key->sk_strategy);
164 : 21003 : matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
165 : : value);
3446 166 : 21003 : break;
167 : 9363 : case BTEqualStrategyNumber:
168 : :
169 : : /*
170 : : * In the equality case (WHERE col = someval), we want to return
171 : : * the current page range if the minimum value in the range <=
172 : : * scan key, and the maximum value >= scan key.
173 : : */
3265 174 : 9363 : finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
175 : : BTLessEqualStrategyNumber);
176 : 9363 : matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
177 : : value);
3446 178 [ + + ]: 9363 : if (!DatumGetBool(matches))
179 : 4722 : break;
180 : : /* max() >= scankey */
3265 181 : 4641 : finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
182 : : BTGreaterEqualStrategyNumber);
183 : 4641 : matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
184 : : value);
3446 185 : 4641 : break;
186 : 22200 : case BTGreaterEqualStrategyNumber:
187 : : case BTGreaterStrategyNumber:
3265 188 : 22200 : finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
189 : 22200 : key->sk_strategy);
190 : 22200 : matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
191 : : value);
3446 192 : 22200 : break;
3446 alvherre@alvh.no-ip. 193 :UBC 0 : default:
194 : : /* shouldn't happen */
195 [ # # ]: 0 : elog(ERROR, "invalid strategy number %d", key->sk_strategy);
196 : : matches = 0;
197 : : break;
198 : : }
199 : :
1115 tomas.vondra@postgre 200 :CBC 52566 : PG_RETURN_DATUM(matches);
201 : : }
202 : :
203 : : /*
204 : : * Given two BrinValues, update the first of them as a union of the summary
205 : : * values contained in both. The second one is untouched.
206 : : */
207 : : Datum
3421 tgl@sss.pgh.pa.us 208 :GBC 15 : brin_minmax_union(PG_FUNCTION_ARGS)
209 : : {
3446 alvherre@alvh.no-ip. 210 : 15 : BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
211 : 15 : BrinValues *col_a = (BrinValues *) PG_GETARG_POINTER(1);
212 : 15 : BrinValues *col_b = (BrinValues *) PG_GETARG_POINTER(2);
213 : 15 : Oid colloid = PG_GET_COLLATION();
214 : : AttrNumber attno;
215 : : Form_pg_attribute attr;
216 : : FmgrInfo *finfo;
217 : : bool needsadj;
218 : :
219 [ - + ]: 15 : Assert(col_a->bv_attno == col_b->bv_attno);
1118 tomas.vondra@postgre 220 [ + - - + ]: 15 : Assert(!col_a->bv_allnulls && !col_b->bv_allnulls);
221 : :
3446 alvherre@alvh.no-ip. 222 : 15 : attno = col_a->bv_attno;
2429 andres@anarazel.de 223 : 15 : attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
224 : :
225 : : /* Adjust minimum, if B's min is less than A's min */
3265 alvherre@alvh.no-ip. 226 : 15 : finfo = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
227 : : BTLessStrategyNumber);
228 : 30 : needsadj = FunctionCall2Coll(finfo, colloid, col_b->bv_values[0],
229 : 15 : col_a->bv_values[0]);
3446 230 [ + + ]: 15 : if (needsadj)
231 : : {
232 [ + - ]: 2 : if (!attr->attbyval)
233 : 2 : pfree(DatumGetPointer(col_a->bv_values[0]));
234 : 2 : col_a->bv_values[0] = datumCopy(col_b->bv_values[0],
235 : 2 : attr->attbyval, attr->attlen);
236 : : }
237 : :
238 : : /* Adjust maximum, if B's max is greater than A's max */
3265 239 : 15 : finfo = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
240 : : BTGreaterStrategyNumber);
241 : 30 : needsadj = FunctionCall2Coll(finfo, colloid, col_b->bv_values[1],
242 : 15 : col_a->bv_values[1]);
3446 243 [ + + ]: 15 : if (needsadj)
244 : : {
245 [ + + ]: 13 : if (!attr->attbyval)
246 : 6 : pfree(DatumGetPointer(col_a->bv_values[1]));
247 : 13 : col_a->bv_values[1] = datumCopy(col_b->bv_values[1],
248 : 13 : attr->attbyval, attr->attlen);
249 : : }
250 : :
251 : 15 : PG_RETURN_VOID();
252 : : }
253 : :
254 : : /*
255 : : * Cache and return the procedure for the given strategy.
256 : : *
257 : : * Note: this function mirrors inclusion_get_strategy_procinfo; see notes
258 : : * there. If changes are made here, see that function too.
259 : : */
260 : : static FmgrInfo *
3265 alvherre@alvh.no-ip. 261 :CBC 866545 : minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype,
262 : : uint16 strategynum)
263 : : {
264 : : MinmaxOpaque *opaque;
265 : :
266 [ + - - + ]: 866545 : Assert(strategynum >= 1 &&
267 : : strategynum <= BTMaxStrategyNumber);
268 : :
3446 269 : 866545 : opaque = (MinmaxOpaque *) bdesc->bd_info[attno - 1]->oi_opaque;
270 : :
271 : : /*
272 : : * We cache the procedures for the previous subtype in the opaque struct,
273 : : * to avoid repetitive syscache lookups. If the subtype changed,
274 : : * invalidate all the cached entries.
275 : : */
3265 276 [ + + ]: 866545 : if (opaque->cached_subtype != subtype)
277 : : {
278 : : uint16 i;
279 : :
280 [ + + ]: 7548 : for (i = 1; i <= BTMaxStrategyNumber; i++)
281 : 6290 : opaque->strategy_procinfos[i - 1].fn_oid = InvalidOid;
282 : 1258 : opaque->cached_subtype = subtype;
283 : : }
284 : :
285 [ + + ]: 866545 : if (opaque->strategy_procinfos[strategynum - 1].fn_oid == InvalidOid)
286 : : {
287 : : Form_pg_attribute attr;
288 : : HeapTuple tuple;
289 : : Oid opfamily,
290 : : oprid;
291 : :
292 : 2081 : opfamily = bdesc->bd_index->rd_opfamily[attno - 1];
2429 andres@anarazel.de 293 : 2081 : attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
3265 alvherre@alvh.no-ip. 294 : 2081 : tuple = SearchSysCache4(AMOPSTRATEGY, ObjectIdGetDatum(opfamily),
295 : : ObjectIdGetDatum(attr->atttypid),
296 : : ObjectIdGetDatum(subtype),
297 : : Int16GetDatum(strategynum));
298 : :
299 [ - + ]: 2081 : if (!HeapTupleIsValid(tuple))
3265 alvherre@alvh.no-ip. 300 [ # # ]:UBC 0 : elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
301 : : strategynum, attr->atttypid, subtype, opfamily);
302 : :
386 dgustafsson@postgres 303 :CBC 2081 : oprid = DatumGetObjectId(SysCacheGetAttrNotNull(AMOPSTRATEGY, tuple,
304 : : Anum_pg_amop_amopopr));
3265 alvherre@alvh.no-ip. 305 : 2081 : ReleaseSysCache(tuple);
386 dgustafsson@postgres 306 [ - + ]: 2081 : Assert(RegProcedureIsValid(oprid));
307 : :
3265 alvherre@alvh.no-ip. 308 : 2081 : fmgr_info_cxt(get_opcode(oprid),
309 : 2081 : &opaque->strategy_procinfos[strategynum - 1],
310 : : bdesc->bd_context);
311 : : }
312 : :
313 : 866545 : return &opaque->strategy_procinfos[strategynum - 1];
314 : : }
|