LCOV - code coverage report
Current view: top level - src/backend/access/brin - brin_minmax.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 97.3 % 110 107
Test Date: 2026-03-02 00:15:17 Functions: 100.0 % 5 5
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*
       2              :  * brin_minmax.c
       3              :  *      Implementation of Min/Max opclass for BRIN
       4              :  *
       5              :  * Portions Copyright (c) 1996-2026, 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        20560 : brin_minmax_opcinfo(PG_FUNCTION_ARGS)
      35              : {
      36        20560 :     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        20560 :     result = palloc0(MAXALIGN(SizeofBrinOpcInfo(2)) +
      45              :                      sizeof(MinmaxOpaque));
      46        20560 :     result->oi_nstored = 2;
      47        20560 :     result->oi_regular_nulls = true;
      48        20560 :     result->oi_opaque = (MinmaxOpaque *)
      49        20560 :         MAXALIGN((char *) result + SizeofBrinOpcInfo(2));
      50        20560 :     result->oi_typcache[0] = result->oi_typcache[1] =
      51        20560 :         lookup_type_cache(typoid, 0);
      52              : 
      53        20560 :     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       414382 : brin_minmax_add_value(PG_FUNCTION_ARGS)
      65              : {
      66       414382 :     BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
      67       414382 :     BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
      68       414382 :     Datum       newval = PG_GETARG_DATUM(2);
      69       414382 :     bool        isnull PG_USED_FOR_ASSERTS_ONLY = PG_GETARG_BOOL(3);
      70       414382 :     Oid         colloid = PG_GET_COLLATION();
      71              :     FmgrInfo   *cmpFn;
      72              :     Datum       compar;
      73       414382 :     bool        updated = false;
      74              :     Form_pg_attribute attr;
      75              :     AttrNumber  attno;
      76              : 
      77              :     Assert(!isnull);
      78              : 
      79       414382 :     attno = column->bv_attno;
      80       414382 :     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       414382 :     if (column->bv_allnulls)
      87              :     {
      88        10470 :         column->bv_values[0] = datumCopy(newval, attr->attbyval, attr->attlen);
      89        10470 :         column->bv_values[1] = datumCopy(newval, attr->attbyval, attr->attlen);
      90        10470 :         column->bv_allnulls = false;
      91        10470 :         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       403912 :     cmpFn = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
     100              :                                          BTLessStrategyNumber);
     101       403912 :     compar = FunctionCall2Coll(cmpFn, colloid, newval, column->bv_values[0]);
     102       403912 :     if (DatumGetBool(compar))
     103              :     {
     104          899 :         if (!attr->attbyval)
     105          689 :             pfree(DatumGetPointer(column->bv_values[0]));
     106          899 :         column->bv_values[0] = datumCopy(newval, attr->attbyval, attr->attlen);
     107          899 :         updated = true;
     108              :     }
     109              : 
     110              :     /*
     111              :      * And now compare it to the existing maximum.
     112              :      */
     113       403912 :     cmpFn = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
     114              :                                          BTGreaterStrategyNumber);
     115       403912 :     compar = FunctionCall2Coll(cmpFn, colloid, newval, column->bv_values[1]);
     116       403912 :     if (DatumGetBool(compar))
     117              :     {
     118       167010 :         if (!attr->attbyval)
     119          444 :             pfree(DatumGetPointer(column->bv_values[1]));
     120       167010 :         column->bv_values[1] = datumCopy(newval, attr->attbyval, attr->attlen);
     121       167010 :         updated = true;
     122              :     }
     123              : 
     124       403912 :     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        52566 : brin_minmax_consistent(PG_FUNCTION_ARGS)
     138              : {
     139        52566 :     BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
     140        52566 :     BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
     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              :     Assert(PG_NARGS() == 3);
     151              : 
     152              :     /* Should not be dealing with all-NULL ranges. */
     153              :     Assert(!column->bv_allnulls);
     154              : 
     155        52566 :     attno = key->sk_attno;
     156        52566 :     subtype = key->sk_subtype;
     157        52566 :     value = key->sk_argument;
     158        52566 :     switch (key->sk_strategy)
     159              :     {
     160        21003 :         case BTLessStrategyNumber:
     161              :         case BTLessEqualStrategyNumber:
     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);
     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              :              */
     174         9363 :             finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
     175              :                                                  BTLessEqualStrategyNumber);
     176         9363 :             matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
     177              :                                         value);
     178         9363 :             if (!DatumGetBool(matches))
     179         4722 :                 break;
     180              :             /* max() >= scankey */
     181         4641 :             finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
     182              :                                                  BTGreaterEqualStrategyNumber);
     183         4641 :             matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
     184              :                                         value);
     185         4641 :             break;
     186        22200 :         case BTGreaterEqualStrategyNumber:
     187              :         case BTGreaterStrategyNumber:
     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);
     192        22200 :             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        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
     208           22 : brin_minmax_union(PG_FUNCTION_ARGS)
     209              : {
     210           22 :     BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
     211           22 :     BrinValues *col_a = (BrinValues *) PG_GETARG_POINTER(1);
     212           22 :     BrinValues *col_b = (BrinValues *) PG_GETARG_POINTER(2);
     213           22 :     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           22 :     attno = col_a->bv_attno;
     223           22 :     attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
     224              : 
     225              :     /* Adjust minimum, if B's min is less than A's min */
     226           22 :     finfo = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
     227              :                                          BTLessStrategyNumber);
     228           22 :     needsadj = DatumGetBool(FunctionCall2Coll(finfo, colloid, col_b->bv_values[0],
     229           22 :                                               col_a->bv_values[0]));
     230           22 :     if (needsadj)
     231              :     {
     232           12 :         if (!attr->attbyval)
     233            8 :             pfree(DatumGetPointer(col_a->bv_values[0]));
     234           12 :         col_a->bv_values[0] = datumCopy(col_b->bv_values[0],
     235           12 :                                         attr->attbyval, attr->attlen);
     236              :     }
     237              : 
     238              :     /* Adjust maximum, if B's max is greater than A's max */
     239           22 :     finfo = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
     240              :                                          BTGreaterStrategyNumber);
     241           22 :     needsadj = DatumGetBool(FunctionCall2Coll(finfo, colloid, col_b->bv_values[1],
     242           22 :                                               col_a->bv_values[1]));
     243           22 :     if (needsadj)
     244              :     {
     245           10 :         if (!attr->attbyval)
     246            3 :             pfree(DatumGetPointer(col_a->bv_values[1]));
     247           10 :         col_a->bv_values[1] = datumCopy(col_b->bv_values[1],
     248           10 :                                         attr->attbyval, attr->attlen);
     249              :     }
     250              : 
     251           22 :     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       865075 : 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       865075 :     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       865075 :     if (opaque->cached_subtype != subtype)
     277              :     {
     278              :         uint16      i;
     279              : 
     280         7626 :         for (i = 1; i <= BTMaxStrategyNumber; i++)
     281         6355 :             opaque->strategy_procinfos[i - 1].fn_oid = InvalidOid;
     282         1271 :         opaque->cached_subtype = subtype;
     283              :     }
     284              : 
     285       865075 :     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         2107 :         opfamily = bdesc->bd_index->rd_opfamily[attno - 1];
     293         2107 :         attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
     294         2107 :         tuple = SearchSysCache4(AMOPSTRATEGY, ObjectIdGetDatum(opfamily),
     295              :                                 ObjectIdGetDatum(attr->atttypid),
     296              :                                 ObjectIdGetDatum(subtype),
     297              :                                 UInt16GetDatum(strategynum));
     298              : 
     299         2107 :         if (!HeapTupleIsValid(tuple))
     300            0 :             elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
     301              :                  strategynum, attr->atttypid, subtype, opfamily);
     302              : 
     303         2107 :         oprid = DatumGetObjectId(SysCacheGetAttrNotNull(AMOPSTRATEGY, tuple,
     304              :                                                         Anum_pg_amop_amopopr));
     305         2107 :         ReleaseSysCache(tuple);
     306              :         Assert(RegProcedureIsValid(oprid));
     307              : 
     308         2107 :         fmgr_info_cxt(get_opcode(oprid),
     309         2107 :                       &opaque->strategy_procinfos[strategynum - 1],
     310              :                       bdesc->bd_context);
     311              :     }
     312              : 
     313       865075 :     return &opaque->strategy_procinfos[strategynum - 1];
     314              : }
        

Generated by: LCOV version 2.0-1