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
34 41058 : brin_minmax_opcinfo(PG_FUNCTION_ARGS)
35 : {
36 41058 : 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 41058 : result = palloc0(MAXALIGN(SizeofBrinOpcInfo(2)) +
45 : sizeof(MinmaxOpaque));
46 41058 : result->oi_nstored = 2;
47 41058 : result->oi_regular_nulls = true;
48 41058 : result->oi_opaque = (MinmaxOpaque *)
49 41058 : MAXALIGN((char *) result + SizeofBrinOpcInfo(2));
50 41058 : result->oi_typcache[0] = result->oi_typcache[1] =
51 41058 : lookup_type_cache(typoid, 0);
52 :
53 41058 : 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
64 828640 : brin_minmax_add_value(PG_FUNCTION_ARGS)
65 : {
66 828640 : BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
67 828640 : BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
68 828640 : Datum newval = PG_GETARG_DATUM(2);
69 828640 : bool isnull PG_USED_FOR_ASSERTS_ONLY = PG_GETARG_DATUM(3);
70 828640 : Oid colloid = PG_GET_COLLATION();
71 : FmgrInfo *cmpFn;
72 : Datum compar;
73 828640 : bool updated = false;
74 : Form_pg_attribute attr;
75 : AttrNumber attno;
76 :
77 : Assert(!isnull);
78 :
79 828640 : attno = column->bv_attno;
80 828640 : 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 : */
86 828640 : if (column->bv_allnulls)
87 : {
88 20880 : column->bv_values[0] = datumCopy(newval, attr->attbyval, attr->attlen);
89 20880 : column->bv_values[1] = datumCopy(newval, attr->attbyval, attr->attlen);
90 20880 : column->bv_allnulls = false;
91 20880 : 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 : */
99 807760 : cmpFn = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
100 : BTLessStrategyNumber);
101 807760 : compar = FunctionCall2Coll(cmpFn, colloid, newval, column->bv_values[0]);
102 807760 : if (DatumGetBool(compar))
103 : {
104 1746 : if (!attr->attbyval)
105 1326 : pfree(DatumGetPointer(column->bv_values[0]));
106 1746 : column->bv_values[0] = datumCopy(newval, attr->attbyval, attr->attlen);
107 1746 : updated = true;
108 : }
109 :
110 : /*
111 : * And now compare it to the existing maximum.
112 : */
113 807760 : cmpFn = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
114 : BTGreaterStrategyNumber);
115 807760 : compar = FunctionCall2Coll(cmpFn, colloid, newval, column->bv_values[1]);
116 807760 : if (DatumGetBool(compar))
117 : {
118 333994 : if (!attr->attbyval)
119 844 : pfree(DatumGetPointer(column->bv_values[1]));
120 333994 : column->bv_values[1] = datumCopy(newval, attr->attbyval, attr->attlen);
121 333994 : updated = true;
122 : }
123 :
124 807760 : 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
137 105132 : brin_minmax_consistent(PG_FUNCTION_ARGS)
138 : {
139 105132 : BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
140 105132 : BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
141 105132 : ScanKey key = (ScanKey) PG_GETARG_POINTER(2);
142 105132 : 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 : Assert(PG_NARGS() == 3);
151 :
152 : /* Should not be dealing with all-NULL ranges. */
153 : Assert(!column->bv_allnulls);
154 :
155 105132 : attno = key->sk_attno;
156 105132 : subtype = key->sk_subtype;
157 105132 : value = key->sk_argument;
158 105132 : switch (key->sk_strategy)
159 : {
160 42006 : case BTLessStrategyNumber:
161 : case BTLessEqualStrategyNumber:
162 42006 : finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
163 42006 : key->sk_strategy);
164 42006 : matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
165 : value);
166 42006 : break;
167 18726 : 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 : */
174 18726 : finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
175 : BTLessEqualStrategyNumber);
176 18726 : matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
177 : value);
178 18726 : if (!DatumGetBool(matches))
179 9444 : break;
180 : /* max() >= scankey */
181 9282 : finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
182 : BTGreaterEqualStrategyNumber);
183 9282 : matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
184 : value);
185 9282 : break;
186 44400 : case BTGreaterEqualStrategyNumber:
187 : case BTGreaterStrategyNumber:
188 44400 : finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
189 44400 : key->sk_strategy);
190 44400 : matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
191 : value);
192 44400 : break;
193 0 : default:
194 : /* shouldn't happen */
195 0 : elog(ERROR, "invalid strategy number %d", key->sk_strategy);
196 : matches = 0;
197 : break;
198 : }
199 :
200 105132 : 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
208 8 : brin_minmax_union(PG_FUNCTION_ARGS)
209 : {
210 8 : BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
211 8 : BrinValues *col_a = (BrinValues *) PG_GETARG_POINTER(1);
212 8 : BrinValues *col_b = (BrinValues *) PG_GETARG_POINTER(2);
213 8 : Oid colloid = PG_GET_COLLATION();
214 : AttrNumber attno;
215 : Form_pg_attribute attr;
216 : FmgrInfo *finfo;
217 : bool needsadj;
218 :
219 : Assert(col_a->bv_attno == col_b->bv_attno);
220 : Assert(!col_a->bv_allnulls && !col_b->bv_allnulls);
221 :
222 8 : attno = col_a->bv_attno;
223 8 : attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
224 :
225 : /* Adjust minimum, if B's min is less than A's min */
226 8 : finfo = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
227 : BTLessStrategyNumber);
228 16 : needsadj = FunctionCall2Coll(finfo, colloid, col_b->bv_values[0],
229 8 : col_a->bv_values[0]);
230 8 : if (needsadj)
231 : {
232 6 : if (!attr->attbyval)
233 4 : pfree(DatumGetPointer(col_a->bv_values[0]));
234 6 : col_a->bv_values[0] = datumCopy(col_b->bv_values[0],
235 6 : attr->attbyval, attr->attlen);
236 : }
237 :
238 : /* Adjust maximum, if B's max is greater than A's max */
239 8 : finfo = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
240 : BTGreaterStrategyNumber);
241 16 : needsadj = FunctionCall2Coll(finfo, colloid, col_b->bv_values[1],
242 8 : col_a->bv_values[1]);
243 8 : if (needsadj)
244 : {
245 8 : if (!attr->attbyval)
246 4 : pfree(DatumGetPointer(col_a->bv_values[1]));
247 8 : col_a->bv_values[1] = datumCopy(col_b->bv_values[1],
248 8 : attr->attbyval, attr->attlen);
249 : }
250 :
251 8 : 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 *
261 1729950 : minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype,
262 : uint16 strategynum)
263 : {
264 : MinmaxOpaque *opaque;
265 :
266 : Assert(strategynum >= 1 &&
267 : strategynum <= BTMaxStrategyNumber);
268 :
269 1729950 : 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 : */
276 1729950 : if (opaque->cached_subtype != subtype)
277 : {
278 : uint16 i;
279 :
280 15084 : for (i = 1; i <= BTMaxStrategyNumber; i++)
281 12570 : opaque->strategy_procinfos[i - 1].fn_oid = InvalidOid;
282 2514 : opaque->cached_subtype = subtype;
283 : }
284 :
285 1729950 : 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 4158 : opfamily = bdesc->bd_index->rd_opfamily[attno - 1];
293 4158 : attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
294 4158 : tuple = SearchSysCache4(AMOPSTRATEGY, ObjectIdGetDatum(opfamily),
295 : ObjectIdGetDatum(attr->atttypid),
296 : ObjectIdGetDatum(subtype),
297 : Int16GetDatum(strategynum));
298 :
299 4158 : if (!HeapTupleIsValid(tuple))
300 0 : elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
301 : strategynum, attr->atttypid, subtype, opfamily);
302 :
303 4158 : oprid = DatumGetObjectId(SysCacheGetAttrNotNull(AMOPSTRATEGY, tuple,
304 : Anum_pg_amop_amopopr));
305 4158 : ReleaseSysCache(tuple);
306 : Assert(RegProcedureIsValid(oprid));
307 :
308 4158 : fmgr_info_cxt(get_opcode(oprid),
309 4158 : &opaque->strategy_procinfos[strategynum - 1],
310 : bdesc->bd_context);
311 : }
312 :
313 1729950 : return &opaque->strategy_procinfos[strategynum - 1];
314 : }
|