LCOV - code coverage report
Current view: top level - src/backend/utils/adt - amutils.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 136 155 87.7 %
Date: 2024-11-21 08:14:44 Functions: 6 7 85.7 %
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-2024, 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        1788 : lookup_prop_name(const char *name)
      91             : {
      92             :     int         i;
      93             : 
      94       17094 :     for (i = 0; i < lengthof(am_propnames); i++)
      95             :     {
      96       16902 :         if (pg_strcasecmp(am_propnames[i].name, name) == 0)
      97        1596 :             return am_propnames[i].prop;
      98             :     }
      99             : 
     100             :     /* We do not throw an error, so that AMs can define their own properties */
     101         192 :     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         336 : 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         336 :     if (!guard)
     126             :     {
     127         168 :         *res = false;
     128         168 :         return true;
     129             :     }
     130             : 
     131         168 :     datum = SysCacheGetAttrNotNull(INDEXRELID, tuple, Anum_pg_index_indoption);
     132             : 
     133         168 :     indoption = ((int2vector *) DatumGetPointer(datum));
     134         168 :     indoption_val = indoption->values[attno - 1];
     135             : 
     136         168 :     *res = (indoption_val & iopt_mask) == iopt_expect;
     137             : 
     138         168 :     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        1788 : indexam_property(FunctionCallInfo fcinfo,
     152             :                  const char *propname,
     153             :                  Oid amoid, Oid index_oid, int attno)
     154             : {
     155        1788 :     bool        res = false;
     156        1788 :     bool        isnull = false;
     157        1788 :     int         natts = 0;
     158             :     IndexAMProperty prop;
     159             :     IndexAmRoutine *routine;
     160             : 
     161             :     /* Try to convert property name to enum (no error if not known) */
     162        1788 :     prop = lookup_prop_name(propname);
     163             : 
     164             :     /* If we have an index OID, look up the AM, and get # of columns too */
     165        1788 :     if (OidIsValid(index_oid))
     166             :     {
     167             :         HeapTuple   tuple;
     168             :         Form_pg_class rd_rel;
     169             : 
     170             :         Assert(!OidIsValid(amoid));
     171        1344 :         tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(index_oid));
     172        1344 :         if (!HeapTupleIsValid(tuple))
     173           0 :             PG_RETURN_NULL();
     174        1344 :         rd_rel = (Form_pg_class) GETSTRUCT(tuple);
     175        1344 :         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        1344 :         amoid = rd_rel->relam;
     182        1344 :         natts = rd_rel->relnatts;
     183        1344 :         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        1788 :     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        1788 :     routine = GetIndexAmRoutineByAmId(amoid, true);
     199        1788 :     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        3198 :     if (routine->amproperty &&
     207        1410 :         routine->amproperty(index_oid, attno, prop, propname,
     208             :                             &res, &isnull))
     209             :     {
     210          66 :         if (isnull)
     211           0 :             PG_RETURN_NULL();
     212          66 :         PG_RETURN_BOOL(res);
     213             :     }
     214             : 
     215        1722 :     if (attno > 0)
     216             :     {
     217             :         HeapTuple   tuple;
     218             :         Form_pg_index rd_index;
     219         870 :         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         870 :         tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
     227         870 :         if (!HeapTupleIsValid(tuple))
     228           0 :             PG_RETURN_NULL();
     229         870 :         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         870 :         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         870 :         if (routine->amcaninclude
     242         690 :             && attno > rd_index->indnkeyatts)
     243          84 :             iskey = false;
     244             : 
     245         870 :         switch (prop)
     246             :         {
     247          96 :             case AMPROP_ASC:
     248         180 :                 if (iskey &&
     249          84 :                     test_indoption(tuple, attno, routine->amcanorder,
     250             :                                    INDOPTION_DESC, 0, &res))
     251          84 :                     isnull = false;
     252          96 :                 break;
     253             : 
     254          96 :             case AMPROP_DESC:
     255         180 :                 if (iskey &&
     256          84 :                     test_indoption(tuple, attno, routine->amcanorder,
     257             :                                    INDOPTION_DESC, INDOPTION_DESC, &res))
     258          84 :                     isnull = false;
     259          96 :                 break;
     260             : 
     261          96 :             case AMPROP_NULLS_FIRST:
     262         180 :                 if (iskey &&
     263          84 :                     test_indoption(tuple, attno, routine->amcanorder,
     264             :                                    INDOPTION_NULLS_FIRST, INDOPTION_NULLS_FIRST, &res))
     265          84 :                     isnull = false;
     266          96 :                 break;
     267             : 
     268          96 :             case AMPROP_NULLS_LAST:
     269         180 :                 if (iskey &&
     270          84 :                     test_indoption(tuple, attno, routine->amcanorder,
     271             :                                    INDOPTION_NULLS_FIRST, 0, &res))
     272          84 :                     isnull = false;
     273          96 :                 break;
     274             : 
     275          96 :             case AMPROP_ORDERABLE:
     276             : 
     277             :                 /*
     278             :                  * generic assumption is that nonkey columns are not orderable
     279             :                  */
     280          96 :                 res = iskey ? routine->amcanorder : false;
     281          96 :                 isnull = false;
     282          96 :                 break;
     283             : 
     284          48 :             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          48 :                 if (!iskey || !routine->amcanorderbyop)
     299             :                 {
     300          48 :                     res = false;
     301          48 :                     isnull = false;
     302             :                 }
     303          48 :                 break;
     304             : 
     305          30 :             case AMPROP_RETURNABLE:
     306             : 
     307             :                 /* note that we ignore iskey for this property */
     308             : 
     309          30 :                 isnull = false;
     310          30 :                 res = false;
     311             : 
     312          30 :                 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          12 :                     Relation    indexrel = index_open(index_oid, AccessShareLock);
     320             : 
     321          12 :                     res = index_can_return(indexrel, attno);
     322          12 :                     index_close(indexrel, AccessShareLock);
     323             :                 }
     324          30 :                 break;
     325             : 
     326          54 :             case AMPROP_SEARCH_ARRAY:
     327          54 :                 if (iskey)
     328             :                 {
     329          54 :                     res = routine->amsearcharray;
     330          54 :                     isnull = false;
     331             :                 }
     332          54 :                 break;
     333             : 
     334          54 :             case AMPROP_SEARCH_NULLS:
     335          54 :                 if (iskey)
     336             :                 {
     337          54 :                     res = routine->amsearchnulls;
     338          54 :                     isnull = false;
     339             :                 }
     340          54 :                 break;
     341             : 
     342         204 :             default:
     343         204 :                 break;
     344             :         }
     345             : 
     346         870 :         ReleaseSysCache(tuple);
     347             : 
     348         870 :         if (!isnull)
     349         618 :             PG_RETURN_BOOL(res);
     350         252 :         PG_RETURN_NULL();
     351             :     }
     352             : 
     353         852 :     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         408 :         switch (prop)
     361             :         {
     362          48 :             case AMPROP_CLUSTERABLE:
     363          48 :                 PG_RETURN_BOOL(routine->amclusterable);
     364             : 
     365          48 :             case AMPROP_INDEX_SCAN:
     366          48 :                 PG_RETURN_BOOL(routine->amgettuple ? true : false);
     367             : 
     368          48 :             case AMPROP_BITMAP_SCAN:
     369          48 :                 PG_RETURN_BOOL(routine->amgetbitmap ? true : false);
     370             : 
     371          48 :             case AMPROP_BACKWARD_SCAN:
     372          48 :                 PG_RETURN_BOOL(routine->amcanbackward);
     373             : 
     374         216 :             default:
     375         216 :                 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         444 :     switch (prop)
     384             :     {
     385          48 :         case AMPROP_CAN_ORDER:
     386          48 :             PG_RETURN_BOOL(routine->amcanorder);
     387             : 
     388          48 :         case AMPROP_CAN_UNIQUE:
     389          48 :             PG_RETURN_BOOL(routine->amcanunique);
     390             : 
     391          48 :         case AMPROP_CAN_MULTI_COL:
     392          48 :             PG_RETURN_BOOL(routine->amcanmulticol);
     393             : 
     394          48 :         case AMPROP_CAN_EXCLUDE:
     395          48 :             PG_RETURN_BOOL(routine->amgettuple ? true : false);
     396             : 
     397          48 :         case AMPROP_CAN_INCLUDE:
     398          48 :             PG_RETURN_BOOL(routine->amcaninclude);
     399             : 
     400         204 :         default:
     401         204 :             PG_RETURN_NULL();
     402             :     }
     403             : }
     404             : 
     405             : /*
     406             :  * Test property of an AM specified by AM OID
     407             :  */
     408             : Datum
     409         444 : pg_indexam_has_property(PG_FUNCTION_ARGS)
     410             : {
     411         444 :     Oid         amoid = PG_GETARG_OID(0);
     412         444 :     char       *propname = text_to_cstring(PG_GETARG_TEXT_PP(1));
     413             : 
     414         444 :     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         408 : pg_index_has_property(PG_FUNCTION_ARGS)
     422             : {
     423         408 :     Oid         relid = PG_GETARG_OID(0);
     424         408 :     char       *propname = text_to_cstring(PG_GETARG_TEXT_PP(1));
     425             : 
     426         408 :     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         936 : pg_index_column_has_property(PG_FUNCTION_ARGS)
     434             : {
     435         936 :     Oid         relid = PG_GETARG_OID(0);
     436         936 :     int32       attno = PG_GETARG_INT32(1);
     437         936 :     char       *propname = text_to_cstring(PG_GETARG_TEXT_PP(2));
     438             : 
     439             :     /* Reject attno 0 immediately, so that attno > 0 identifies this case */
     440         936 :     if (attno <= 0)
     441           0 :         PG_RETURN_NULL();
     442             : 
     443         936 :     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             :     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 1.14