LCOV - code coverage report
Current view: top level - src/backend/utils/adt - amutils.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 87.7 % 155 136
Test Date: 2026-03-12 06:14:44 Functions: 85.7 % 7 6
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * amutils.c
       4              :  *    SQL-level APIs related to index access methods.
       5              :  *
       6              :  * Copyright (c) 2016-2026, PostgreSQL Global Development Group
       7              :  *
       8              :  *
       9              :  * IDENTIFICATION
      10              :  *    src/backend/utils/adt/amutils.c
      11              :  *
      12              :  *-------------------------------------------------------------------------
      13              :  */
      14              : #include "postgres.h"
      15              : 
      16              : #include "access/amapi.h"
      17              : #include "access/htup_details.h"
      18              : #include "catalog/pg_class.h"
      19              : #include "catalog/pg_index.h"
      20              : #include "utils/builtins.h"
      21              : #include "utils/syscache.h"
      22              : 
      23              : 
      24              : /* Convert string property name to enum, for efficiency */
      25              : struct am_propname
      26              : {
      27              :     const char *name;
      28              :     IndexAMProperty prop;
      29              : };
      30              : 
      31              : static const struct am_propname am_propnames[] =
      32              : {
      33              :     {
      34              :         "asc", AMPROP_ASC
      35              :     },
      36              :     {
      37              :         "desc", AMPROP_DESC
      38              :     },
      39              :     {
      40              :         "nulls_first", AMPROP_NULLS_FIRST
      41              :     },
      42              :     {
      43              :         "nulls_last", AMPROP_NULLS_LAST
      44              :     },
      45              :     {
      46              :         "orderable", AMPROP_ORDERABLE
      47              :     },
      48              :     {
      49              :         "distance_orderable", AMPROP_DISTANCE_ORDERABLE
      50              :     },
      51              :     {
      52              :         "returnable", AMPROP_RETURNABLE
      53              :     },
      54              :     {
      55              :         "search_array", AMPROP_SEARCH_ARRAY
      56              :     },
      57              :     {
      58              :         "search_nulls", AMPROP_SEARCH_NULLS
      59              :     },
      60              :     {
      61              :         "clusterable", AMPROP_CLUSTERABLE
      62              :     },
      63              :     {
      64              :         "index_scan", AMPROP_INDEX_SCAN
      65              :     },
      66              :     {
      67              :         "bitmap_scan", AMPROP_BITMAP_SCAN
      68              :     },
      69              :     {
      70              :         "backward_scan", AMPROP_BACKWARD_SCAN
      71              :     },
      72              :     {
      73              :         "can_order", AMPROP_CAN_ORDER
      74              :     },
      75              :     {
      76              :         "can_unique", AMPROP_CAN_UNIQUE
      77              :     },
      78              :     {
      79              :         "can_multi_col", AMPROP_CAN_MULTI_COL
      80              :     },
      81              :     {
      82              :         "can_exclude", AMPROP_CAN_EXCLUDE
      83              :     },
      84              :     {
      85              :         "can_include", AMPROP_CAN_INCLUDE
      86              :     },
      87              : };
      88              : 
      89              : static IndexAMProperty
      90          894 : lookup_prop_name(const char *name)
      91              : {
      92              :     int         i;
      93              : 
      94         8547 :     for (i = 0; i < lengthof(am_propnames); i++)
      95              :     {
      96         8451 :         if (pg_strcasecmp(am_propnames[i].name, name) == 0)
      97          798 :             return am_propnames[i].prop;
      98              :     }
      99              : 
     100              :     /* We do not throw an error, so that AMs can define their own properties */
     101           96 :     return AMPROP_UNKNOWN;
     102              : }
     103              : 
     104              : /*
     105              :  * Common code for properties that are just bit tests of indoptions.
     106              :  *
     107              :  * tuple: the pg_index heaptuple
     108              :  * attno: identify the index column to test the indoptions of.
     109              :  * guard: if false, a boolean false result is forced (saves code in caller).
     110              :  * iopt_mask: mask for interesting indoption bit.
     111              :  * iopt_expect: value for a "true" result (should be 0 or iopt_mask).
     112              :  *
     113              :  * Returns false to indicate a NULL result (for "unknown/inapplicable"),
     114              :  * otherwise sets *res to the boolean value to return.
     115              :  */
     116              : static bool
     117          168 : test_indoption(HeapTuple tuple, int attno, bool guard,
     118              :                int16 iopt_mask, int16 iopt_expect,
     119              :                bool *res)
     120              : {
     121              :     Datum       datum;
     122              :     int2vector *indoption;
     123              :     int16       indoption_val;
     124              : 
     125          168 :     if (!guard)
     126              :     {
     127           84 :         *res = false;
     128           84 :         return true;
     129              :     }
     130              : 
     131           84 :     datum = SysCacheGetAttrNotNull(INDEXRELID, tuple, Anum_pg_index_indoption);
     132              : 
     133           84 :     indoption = ((int2vector *) DatumGetPointer(datum));
     134           84 :     indoption_val = indoption->values[attno - 1];
     135              : 
     136           84 :     *res = (indoption_val & iopt_mask) == iopt_expect;
     137              : 
     138           84 :     return true;
     139              : }
     140              : 
     141              : 
     142              : /*
     143              :  * Test property of an index AM, index, or index column.
     144              :  *
     145              :  * This is common code for different SQL-level funcs, so the amoid and
     146              :  * index_oid parameters are mutually exclusive; we look up the amoid from the
     147              :  * index_oid if needed, or if no index oid is given, we're looking at AM-wide
     148              :  * properties.
     149              :  */
     150              : static Datum
     151          894 : indexam_property(FunctionCallInfo fcinfo,
     152              :                  const char *propname,
     153              :                  Oid amoid, Oid index_oid, int attno)
     154              : {
     155          894 :     bool        res = false;
     156          894 :     bool        isnull = false;
     157          894 :     int         natts = 0;
     158              :     IndexAMProperty prop;
     159              :     const IndexAmRoutine *routine;
     160              : 
     161              :     /* Try to convert property name to enum (no error if not known) */
     162          894 :     prop = lookup_prop_name(propname);
     163              : 
     164              :     /* If we have an index OID, look up the AM, and get # of columns too */
     165          894 :     if (OidIsValid(index_oid))
     166              :     {
     167              :         HeapTuple   tuple;
     168              :         Form_pg_class rd_rel;
     169              : 
     170              :         Assert(!OidIsValid(amoid));
     171          672 :         tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(index_oid));
     172          672 :         if (!HeapTupleIsValid(tuple))
     173            0 :             PG_RETURN_NULL();
     174          672 :         rd_rel = (Form_pg_class) GETSTRUCT(tuple);
     175          672 :         if (rd_rel->relkind != RELKIND_INDEX &&
     176            0 :             rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
     177              :         {
     178            0 :             ReleaseSysCache(tuple);
     179            0 :             PG_RETURN_NULL();
     180              :         }
     181          672 :         amoid = rd_rel->relam;
     182          672 :         natts = rd_rel->relnatts;
     183          672 :         ReleaseSysCache(tuple);
     184              :     }
     185              : 
     186              :     /*
     187              :      * At this point, either index_oid == InvalidOid or it's a valid index
     188              :      * OID. Also, after this test and the one below, either attno == 0 for
     189              :      * index-wide or AM-wide tests, or it's a valid column number in a valid
     190              :      * index.
     191              :      */
     192          894 :     if (attno < 0 || attno > natts)
     193            0 :         PG_RETURN_NULL();
     194              : 
     195              :     /*
     196              :      * Get AM information.  If we don't have a valid AM OID, return NULL.
     197              :      */
     198          894 :     routine = GetIndexAmRoutineByAmId(amoid, true);
     199          894 :     if (routine == NULL)
     200            0 :         PG_RETURN_NULL();
     201              : 
     202              :     /*
     203              :      * If there's an AM property routine, give it a chance to override the
     204              :      * generic logic.  Proceed if it returns false.
     205              :      */
     206         1599 :     if (routine->amproperty &&
     207          705 :         routine->amproperty(index_oid, attno, prop, propname,
     208              :                             &res, &isnull))
     209              :     {
     210           33 :         if (isnull)
     211            0 :             PG_RETURN_NULL();
     212           33 :         PG_RETURN_BOOL(res);
     213              :     }
     214              : 
     215          861 :     if (attno > 0)
     216              :     {
     217              :         HeapTuple   tuple;
     218              :         Form_pg_index rd_index;
     219          435 :         bool        iskey = true;
     220              : 
     221              :         /*
     222              :          * Handle column-level properties. Many of these need the pg_index row
     223              :          * (which we also need to use to check for nonkey atts) so we fetch
     224              :          * that first.
     225              :          */
     226          435 :         tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
     227          435 :         if (!HeapTupleIsValid(tuple))
     228            0 :             PG_RETURN_NULL();
     229          435 :         rd_index = (Form_pg_index) GETSTRUCT(tuple);
     230              : 
     231              :         Assert(index_oid == rd_index->indexrelid);
     232              :         Assert(attno > 0 && attno <= rd_index->indnatts);
     233              : 
     234          435 :         isnull = true;
     235              : 
     236              :         /*
     237              :          * If amcaninclude, we might be looking at an attno for a nonkey
     238              :          * column, for which we (generically) assume that most properties are
     239              :          * null.
     240              :          */
     241          435 :         if (routine->amcaninclude
     242          345 :             && attno > rd_index->indnkeyatts)
     243           42 :             iskey = false;
     244              : 
     245          435 :         switch (prop)
     246              :         {
     247           48 :             case AMPROP_ASC:
     248           90 :                 if (iskey &&
     249           42 :                     test_indoption(tuple, attno, routine->amcanorder,
     250              :                                    INDOPTION_DESC, 0, &res))
     251           42 :                     isnull = false;
     252           48 :                 break;
     253              : 
     254           48 :             case AMPROP_DESC:
     255           90 :                 if (iskey &&
     256           42 :                     test_indoption(tuple, attno, routine->amcanorder,
     257              :                                    INDOPTION_DESC, INDOPTION_DESC, &res))
     258           42 :                     isnull = false;
     259           48 :                 break;
     260              : 
     261           48 :             case AMPROP_NULLS_FIRST:
     262           90 :                 if (iskey &&
     263           42 :                     test_indoption(tuple, attno, routine->amcanorder,
     264              :                                    INDOPTION_NULLS_FIRST, INDOPTION_NULLS_FIRST, &res))
     265           42 :                     isnull = false;
     266           48 :                 break;
     267              : 
     268           48 :             case AMPROP_NULLS_LAST:
     269           90 :                 if (iskey &&
     270           42 :                     test_indoption(tuple, attno, routine->amcanorder,
     271              :                                    INDOPTION_NULLS_FIRST, 0, &res))
     272           42 :                     isnull = false;
     273           48 :                 break;
     274              : 
     275           48 :             case AMPROP_ORDERABLE:
     276              : 
     277              :                 /*
     278              :                  * generic assumption is that nonkey columns are not orderable
     279              :                  */
     280           48 :                 res = iskey ? routine->amcanorder : false;
     281           48 :                 isnull = false;
     282           48 :                 break;
     283              : 
     284           24 :             case AMPROP_DISTANCE_ORDERABLE:
     285              : 
     286              :                 /*
     287              :                  * The conditions for whether a column is distance-orderable
     288              :                  * are really up to the AM (at time of writing, only GiST
     289              :                  * supports it at all). The planner has its own idea based on
     290              :                  * whether it finds an operator with amoppurpose 'o', but
     291              :                  * getting there from just the index column type seems like a
     292              :                  * lot of work. So instead we expect the AM to handle this in
     293              :                  * its amproperty routine. The generic result is to return
     294              :                  * false if the AM says it never supports this, or if this is
     295              :                  * a nonkey column, and null otherwise (meaning we don't
     296              :                  * know).
     297              :                  */
     298           24 :                 if (!iskey || !routine->amcanorderbyop)
     299              :                 {
     300           24 :                     res = false;
     301           24 :                     isnull = false;
     302              :                 }
     303           24 :                 break;
     304              : 
     305           15 :             case AMPROP_RETURNABLE:
     306              : 
     307              :                 /* note that we ignore iskey for this property */
     308              : 
     309           15 :                 isnull = false;
     310           15 :                 res = false;
     311              : 
     312           15 :                 if (routine->amcanreturn)
     313              :                 {
     314              :                     /*
     315              :                      * If possible, the AM should handle this test in its
     316              :                      * amproperty function without opening the rel. But this
     317              :                      * is the generic fallback if it does not.
     318              :                      */
     319            6 :                     Relation    indexrel = index_open(index_oid, AccessShareLock);
     320              : 
     321            6 :                     res = index_can_return(indexrel, attno);
     322            6 :                     index_close(indexrel, AccessShareLock);
     323              :                 }
     324           15 :                 break;
     325              : 
     326           27 :             case AMPROP_SEARCH_ARRAY:
     327           27 :                 if (iskey)
     328              :                 {
     329           27 :                     res = routine->amsearcharray;
     330           27 :                     isnull = false;
     331              :                 }
     332           27 :                 break;
     333              : 
     334           27 :             case AMPROP_SEARCH_NULLS:
     335           27 :                 if (iskey)
     336              :                 {
     337           27 :                     res = routine->amsearchnulls;
     338           27 :                     isnull = false;
     339              :                 }
     340           27 :                 break;
     341              : 
     342          102 :             default:
     343          102 :                 break;
     344              :         }
     345              : 
     346          435 :         ReleaseSysCache(tuple);
     347              : 
     348          435 :         if (!isnull)
     349          309 :             PG_RETURN_BOOL(res);
     350          126 :         PG_RETURN_NULL();
     351              :     }
     352              : 
     353          426 :     if (OidIsValid(index_oid))
     354              :     {
     355              :         /*
     356              :          * Handle index-level properties.  Currently, these only depend on the
     357              :          * AM, but that might not be true forever, so we make users name an
     358              :          * index not just an AM.
     359              :          */
     360          204 :         switch (prop)
     361              :         {
     362           24 :             case AMPROP_CLUSTERABLE:
     363           24 :                 PG_RETURN_BOOL(routine->amclusterable);
     364              : 
     365           24 :             case AMPROP_INDEX_SCAN:
     366           24 :                 PG_RETURN_BOOL(routine->amgettuple ? true : false);
     367              : 
     368           24 :             case AMPROP_BITMAP_SCAN:
     369           24 :                 PG_RETURN_BOOL(routine->amgetbitmap ? true : false);
     370              : 
     371           24 :             case AMPROP_BACKWARD_SCAN:
     372           24 :                 PG_RETURN_BOOL(routine->amcanbackward);
     373              : 
     374          108 :             default:
     375          108 :                 PG_RETURN_NULL();
     376              :         }
     377              :     }
     378              : 
     379              :     /*
     380              :      * Handle AM-level properties (those that control what you can say in
     381              :      * CREATE INDEX).
     382              :      */
     383          222 :     switch (prop)
     384              :     {
     385           24 :         case AMPROP_CAN_ORDER:
     386           24 :             PG_RETURN_BOOL(routine->amcanorder);
     387              : 
     388           24 :         case AMPROP_CAN_UNIQUE:
     389           24 :             PG_RETURN_BOOL(routine->amcanunique);
     390              : 
     391           24 :         case AMPROP_CAN_MULTI_COL:
     392           24 :             PG_RETURN_BOOL(routine->amcanmulticol);
     393              : 
     394           24 :         case AMPROP_CAN_EXCLUDE:
     395           24 :             PG_RETURN_BOOL(routine->amgettuple ? true : false);
     396              : 
     397           24 :         case AMPROP_CAN_INCLUDE:
     398           24 :             PG_RETURN_BOOL(routine->amcaninclude);
     399              : 
     400          102 :         default:
     401          102 :             PG_RETURN_NULL();
     402              :     }
     403              : }
     404              : 
     405              : /*
     406              :  * Test property of an AM specified by AM OID
     407              :  */
     408              : Datum
     409          222 : pg_indexam_has_property(PG_FUNCTION_ARGS)
     410              : {
     411          222 :     Oid         amoid = PG_GETARG_OID(0);
     412          222 :     char       *propname = text_to_cstring(PG_GETARG_TEXT_PP(1));
     413              : 
     414          222 :     return indexam_property(fcinfo, propname, amoid, InvalidOid, 0);
     415              : }
     416              : 
     417              : /*
     418              :  * Test property of an index specified by index OID
     419              :  */
     420              : Datum
     421          204 : pg_index_has_property(PG_FUNCTION_ARGS)
     422              : {
     423          204 :     Oid         relid = PG_GETARG_OID(0);
     424          204 :     char       *propname = text_to_cstring(PG_GETARG_TEXT_PP(1));
     425              : 
     426          204 :     return indexam_property(fcinfo, propname, InvalidOid, relid, 0);
     427              : }
     428              : 
     429              : /*
     430              :  * Test property of an index column specified by index OID and column number
     431              :  */
     432              : Datum
     433          468 : pg_index_column_has_property(PG_FUNCTION_ARGS)
     434              : {
     435          468 :     Oid         relid = PG_GETARG_OID(0);
     436          468 :     int32       attno = PG_GETARG_INT32(1);
     437          468 :     char       *propname = text_to_cstring(PG_GETARG_TEXT_PP(2));
     438              : 
     439              :     /* Reject attno 0 immediately, so that attno > 0 identifies this case */
     440          468 :     if (attno <= 0)
     441            0 :         PG_RETURN_NULL();
     442              : 
     443          468 :     return indexam_property(fcinfo, propname, InvalidOid, relid, attno);
     444              : }
     445              : 
     446              : /*
     447              :  * Return the name of the given phase, as used for progress reporting by the
     448              :  * given AM.
     449              :  */
     450              : Datum
     451            0 : pg_indexam_progress_phasename(PG_FUNCTION_ARGS)
     452              : {
     453            0 :     Oid         amoid = PG_GETARG_OID(0);
     454            0 :     int32       phasenum = PG_GETARG_INT32(1);
     455              :     const IndexAmRoutine *routine;
     456              :     char       *name;
     457              : 
     458            0 :     routine = GetIndexAmRoutineByAmId(amoid, true);
     459            0 :     if (routine == NULL || !routine->ambuildphasename)
     460            0 :         PG_RETURN_NULL();
     461              : 
     462            0 :     name = routine->ambuildphasename(phasenum);
     463            0 :     if (!name)
     464            0 :         PG_RETURN_NULL();
     465              : 
     466            0 :     PG_RETURN_DATUM(CStringGetTextDatum(name));
     467              : }
        

Generated by: LCOV version 2.0-1