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