Age Owner TLA Line data Source code
1 : /*
2 : * brin_minmax.c
3 : * Implementation of Min/Max opclass for BRIN
4 : *
5 : * Portions Copyright (c) 1996-2023, 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
3050 tgl 36 CBC 20378 : brin_minmax_opcinfo(PG_FUNCTION_ARGS)
37 : {
3075 alvherre 38 20378 : 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 20378 : result = palloc0(MAXALIGN(SizeofBrinOpcInfo(2)) +
47 : sizeof(MinmaxOpaque));
48 20378 : result->oi_nstored = 2;
747 tomas.vondra 49 20378 : result->oi_regular_nulls = true;
3075 alvherre 50 20378 : result->oi_opaque = (MinmaxOpaque *)
51 20378 : MAXALIGN((char *) result + SizeofBrinOpcInfo(2));
2894 52 20378 : result->oi_typcache[0] = result->oi_typcache[1] =
53 20378 : lookup_type_cache(typoid, 0);
54 :
3075 55 20378 : 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
3050 tgl 66 346142 : brin_minmax_add_value(PG_FUNCTION_ARGS)
67 : {
3075 alvherre 68 346142 : BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
69 346142 : BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
70 346142 : Datum newval = PG_GETARG_DATUM(2);
747 tomas.vondra 71 346142 : bool isnull PG_USED_FOR_ASSERTS_ONLY = PG_GETARG_DATUM(3);
3075 alvherre 72 346142 : Oid colloid = PG_GET_COLLATION();
73 : FmgrInfo *cmpFn;
74 : Datum compar;
75 346142 : bool updated = false;
76 : Form_pg_attribute attr;
77 : AttrNumber attno;
78 :
747 tomas.vondra 79 346142 : Assert(!isnull);
80 :
3075 alvherre 81 346142 : attno = column->bv_attno;
2058 andres 82 346142 : 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 : */
3075 alvherre 88 346142 : if (column->bv_allnulls)
89 : {
90 10307 : column->bv_values[0] = datumCopy(newval, attr->attbyval, attr->attlen);
91 10307 : column->bv_values[1] = datumCopy(newval, attr->attbyval, attr->attlen);
92 10307 : column->bv_allnulls = false;
93 10307 : 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 : */
2894 101 335835 : cmpFn = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
102 : BTLessStrategyNumber);
3075 103 335835 : compar = FunctionCall2Coll(cmpFn, colloid, newval, column->bv_values[0]);
104 335835 : if (DatumGetBool(compar))
105 : {
106 537 : if (!attr->attbyval)
107 327 : pfree(DatumGetPointer(column->bv_values[0]));
108 537 : column->bv_values[0] = datumCopy(newval, attr->attbyval, attr->attlen);
109 537 : updated = true;
110 : }
111 :
112 : /*
113 : * And now compare it to the existing maximum.
114 : */
2894 115 335835 : cmpFn = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
116 : BTGreaterStrategyNumber);
3075 117 335835 : compar = FunctionCall2Coll(cmpFn, colloid, newval, column->bv_values[1]);
118 335835 : if (DatumGetBool(compar))
119 : {
120 144875 : if (!attr->attbyval)
121 180 : pfree(DatumGetPointer(column->bv_values[1]));
122 144875 : column->bv_values[1] = datumCopy(newval, attr->attbyval, attr->attlen);
123 144875 : updated = true;
124 : }
125 :
126 335835 : 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
3050 tgl 139 52566 : brin_minmax_consistent(PG_FUNCTION_ARGS)
140 : {
3075 alvherre 141 52566 : BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
142 52566 : BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
744 tomas.vondra 143 52566 : ScanKey key = (ScanKey) PG_GETARG_POINTER(2);
144 52566 : 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 52566 : Assert(PG_NARGS() == 3);
153 :
154 : /* Should not be dealing with all-NULL ranges. */
155 52566 : Assert(!column->bv_allnulls);
156 :
157 52566 : attno = key->sk_attno;
158 52566 : subtype = key->sk_subtype;
159 52566 : value = key->sk_argument;
3075 alvherre 160 52566 : switch (key->sk_strategy)
161 : {
162 21003 : case BTLessStrategyNumber:
163 : case BTLessEqualStrategyNumber:
2894 164 21003 : finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
165 21003 : key->sk_strategy);
166 21003 : matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
167 : value);
3075 168 21003 : break;
169 9363 : 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 : */
2894 176 9363 : finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
177 : BTLessEqualStrategyNumber);
178 9363 : matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
179 : value);
3075 180 9363 : if (!DatumGetBool(matches))
181 4722 : break;
182 : /* max() >= scankey */
2894 183 4641 : finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
184 : BTGreaterEqualStrategyNumber);
185 4641 : matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
186 : value);
3075 187 4641 : break;
188 22200 : case BTGreaterEqualStrategyNumber:
189 : case BTGreaterStrategyNumber:
2894 190 22200 : finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
191 22200 : key->sk_strategy);
192 22200 : matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
193 : value);
3075 194 22200 : break;
3075 alvherre 195 UBC 0 : default:
196 : /* shouldn't happen */
197 0 : elog(ERROR, "invalid strategy number %d", key->sk_strategy);
198 : matches = 0;
199 : break;
200 : }
201 :
744 tomas.vondra 202 CBC 52566 : 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
3050 tgl 210 UBC 0 : brin_minmax_union(PG_FUNCTION_ARGS)
211 : {
3075 alvherre 212 0 : BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
213 0 : BrinValues *col_a = (BrinValues *) PG_GETARG_POINTER(1);
214 0 : BrinValues *col_b = (BrinValues *) PG_GETARG_POINTER(2);
215 0 : Oid colloid = PG_GET_COLLATION();
216 : AttrNumber attno;
217 : Form_pg_attribute attr;
218 : FmgrInfo *finfo;
219 : bool needsadj;
220 :
221 0 : Assert(col_a->bv_attno == col_b->bv_attno);
747 tomas.vondra 222 0 : Assert(!col_a->bv_allnulls && !col_b->bv_allnulls);
223 :
3075 alvherre 224 0 : attno = col_a->bv_attno;
2058 andres 225 0 : attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
226 :
227 : /* Adjust minimum, if B's min is less than A's min */
2894 alvherre 228 0 : finfo = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
229 : BTLessStrategyNumber);
230 0 : needsadj = FunctionCall2Coll(finfo, colloid, col_b->bv_values[0],
231 0 : col_a->bv_values[0]);
3075 232 0 : if (needsadj)
233 : {
234 0 : if (!attr->attbyval)
235 0 : pfree(DatumGetPointer(col_a->bv_values[0]));
236 0 : col_a->bv_values[0] = datumCopy(col_b->bv_values[0],
237 0 : attr->attbyval, attr->attlen);
238 : }
239 :
240 : /* Adjust maximum, if B's max is greater than A's max */
2894 241 0 : finfo = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
242 : BTGreaterStrategyNumber);
243 0 : needsadj = FunctionCall2Coll(finfo, colloid, col_b->bv_values[1],
244 0 : col_a->bv_values[1]);
3075 245 0 : if (needsadj)
246 : {
247 0 : if (!attr->attbyval)
248 0 : pfree(DatumGetPointer(col_a->bv_values[1]));
249 0 : col_a->bv_values[1] = datumCopy(col_b->bv_values[1],
250 0 : attr->attbyval, attr->attlen);
251 : }
252 :
253 0 : 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 *
2894 alvherre 263 CBC 728877 : minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype,
264 : uint16 strategynum)
265 : {
266 : MinmaxOpaque *opaque;
267 :
268 728877 : Assert(strategynum >= 1 &&
269 : strategynum <= BTMaxStrategyNumber);
270 :
3075 271 728877 : 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 : */
2894 278 728877 : if (opaque->cached_subtype != subtype)
279 : {
280 : uint16 i;
281 :
282 7452 : for (i = 1; i <= BTMaxStrategyNumber; i++)
283 6210 : opaque->strategy_procinfos[i - 1].fn_oid = InvalidOid;
284 1242 : opaque->cached_subtype = subtype;
285 : }
286 :
287 728877 : if (opaque->strategy_procinfos[strategynum - 1].fn_oid == InvalidOid)
288 : {
289 : Form_pg_attribute attr;
290 : HeapTuple tuple;
291 : Oid opfamily,
292 : oprid;
2894 alvherre 293 ECB :
2894 alvherre 294 CBC 2049 : opfamily = bdesc->bd_index->rd_opfamily[attno - 1];
2058 andres 295 2049 : attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
2894 alvherre 296 GIC 2049 : tuple = SearchSysCache4(AMOPSTRATEGY, ObjectIdGetDatum(opfamily),
297 : ObjectIdGetDatum(attr->atttypid),
298 : ObjectIdGetDatum(subtype),
299 : Int16GetDatum(strategynum));
2894 alvherre 300 ECB :
2894 alvherre 301 GBC 2049 : if (!HeapTupleIsValid(tuple))
2894 alvherre 302 UIC 0 : elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
303 : strategynum, attr->atttypid, subtype, opfamily);
2894 alvherre 304 ECB :
15 dgustafsson 305 GNC 2049 : oprid = DatumGetObjectId(SysCacheGetAttrNotNull(AMOPSTRATEGY, tuple,
306 : Anum_pg_amop_amopopr));
2894 alvherre 307 CBC 2049 : ReleaseSysCache(tuple);
15 dgustafsson 308 GNC 2049 : Assert(RegProcedureIsValid(oprid));
2894 alvherre 309 ECB :
2894 alvherre 310 CBC 2049 : fmgr_info_cxt(get_opcode(oprid),
2894 alvherre 311 GIC 2049 : &opaque->strategy_procinfos[strategynum - 1],
312 : bdesc->bd_context);
313 : }
3075 alvherre 314 ECB :
2894 alvherre 315 GIC 728877 : return &opaque->strategy_procinfos[strategynum - 1];
316 : }
|