Coverage Report

Created: 2025-10-09 06:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}