/*-------------------------------------------------------------------------
*
* amutils . c
* SQL - level APIs related to index access methods .
*
* Copyright ( c ) 2016 - 2025 , PostgreSQL Global Development Group
*
*
* IDENTIFICATION
* src / backend / utils / adt / amutils . c
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
#include "postgres.h"
#include "access/amapi.h"
#include "access/htup_details.h"
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "utils/builtins.h"
#include "utils/syscache.h"
/* Convert string property name to enum, for efficiency */
struct am_propname
{
const char *name;
IndexAMProperty prop;
};
static const struct am_propname am_propnames[] =
{
{
"asc" , AMPROP_ASC
},
{
"desc" , AMPROP_DESC
},
{
"nulls_first" , AMPROP_NULLS_FIRST
},
{
"nulls_last" , AMPROP_NULLS_LAST
},
{
"orderable" , AMPROP_ORDERABLE
},
{
"distance_orderable" , AMPROP_DISTANCE_ORDERABLE
},
{
"returnable" , AMPROP_RETURNABLE
},
{
"search_array" , AMPROP_SEARCH_ARRAY
},
{
"search_nulls" , AMPROP_SEARCH_NULLS
},
{
"clusterable" , AMPROP_CLUSTERABLE
},
{
"index_scan" , AMPROP_INDEX_SCAN
},
{
"bitmap_scan" , AMPROP_BITMAP_SCAN
},
{
"backward_scan" , AMPROP_BACKWARD_SCAN
},
{
"can_order" , AMPROP_CAN_ORDER
},
{
"can_unique" , AMPROP_CAN_UNIQUE
},
{
"can_multi_col" , AMPROP_CAN_MULTI_COL
},
{
"can_exclude" , AMPROP_CAN_EXCLUDE
},
{
"can_include" , AMPROP_CAN_INCLUDE
},
};
static IndexAMProperty
lookup_prop_name(const char *name)
{
int i;
for (i = 0 ; i < lengthof(am_propnames); i++)
{
if (pg_strcasecmp(am_propnames[i].name, name) == 0 )
return am_propnames[i].prop;
}
/* We do not throw an error, so that AMs can define their own properties */
return AMPROP_UNKNOWN;
}
/*
* Common code for properties that are just bit tests of indoptions .
*
* tuple : the pg_index heaptuple
* attno : identify the index column to test the indoptions of .
* guard : if false , a boolean false result is forced ( saves code in caller ) .
* iopt_mask : mask for interesting indoption bit .
* iopt_expect : value for a " true " result ( should be 0 or iopt_mask ) .
*
* Returns false to indicate a NULL result ( for " unknown / inapplicable " ) ,
* otherwise sets * res to the boolean value to return .
*/
static bool
test_indoption(HeapTuple tuple, int attno, bool guard,
int16 iopt_mask, int16 iopt_expect,
bool *res)
{
Datum datum;
int2vector *indoption;
int16 indoption_val;
if (!guard)
{
*res = false ;
return true ;
}
datum = SysCacheGetAttrNotNull(INDEXRELID, tuple, Anum_pg_index_indoption);
indoption = ((int2vector *) DatumGetPointer(datum));
indoption_val = indoption->values[attno - 1 ];
*res = (indoption_val & iopt_mask) == iopt_expect;
return true ;
}
/*
* Test property of an index AM , index , or index column .
*
* This is common code for different SQL - level funcs , so the amoid and
* index_oid parameters are mutually exclusive ; we look up the amoid from the
* index_oid if needed , or if no index oid is given , we ' re looking at AM - wide
* properties .
*/
static Datum
indexam_property(FunctionCallInfo fcinfo,
const char *propname,
Oid amoid, Oid index_oid, int attno)
{
bool res = false ;
bool isnull = false ;
int natts = 0 ;
IndexAMProperty prop;
IndexAmRoutine *routine;
/* Try to convert property name to enum (no error if not known) */
prop = lookup_prop_name(propname);
/* If we have an index OID, look up the AM, and get # of columns too */
if (OidIsValid(index_oid))
{
HeapTuple tuple;
Form_pg_class rd_rel;
Assert(!OidIsValid(amoid));
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(index_oid));
if (!HeapTupleIsValid(tuple))
PG_RETURN_NULL();
rd_rel = (Form_pg_class) GETSTRUCT(tuple);
if (rd_rel->relkind != RELKIND_INDEX &&
rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
{
ReleaseSysCache(tuple);
PG_RETURN_NULL();
}
amoid = rd_rel->relam;
natts = rd_rel->relnatts;
ReleaseSysCache(tuple);
}
/*
* At this point , either index_oid = = InvalidOid or it ' s a valid index
* OID . Also , after this test and the one below , either attno = = 0 for
* index - wide or AM - wide tests , or it ' s a valid column number in a valid
* index .
*/
if (attno < 0 || attno > natts)
PG_RETURN_NULL();
/*
* Get AM information . If we don ' t have a valid AM OID , return NULL .
*/
routine = GetIndexAmRoutineByAmId(amoid, true );
if (routine == NULL)
PG_RETURN_NULL();
/*
* If there ' s an AM property routine , give it a chance to override the
* generic logic . Proceed if it returns false .
*/
if (routine->amproperty &&
routine->amproperty(index_oid, attno, prop, propname,
&res, &isnull))
{
if (isnull)
PG_RETURN_NULL();
PG_RETURN_BOOL(res);
}
if (attno > 0 )
{
HeapTuple tuple;
Form_pg_index rd_index;
bool iskey = true ;
/*
* Handle column - level properties . Many of these need the pg_index row
* ( which we also need to use to check for nonkey atts ) so we fetch
* that first .
*/
tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
if (!HeapTupleIsValid(tuple))
PG_RETURN_NULL();
rd_index = (Form_pg_index) GETSTRUCT(tuple);
Assert(index_oid == rd_index->indexrelid);
Assert(attno > 0 && attno <= rd_index->indnatts);
isnull = true ;
/*
* If amcaninclude , we might be looking at an attno for a nonkey
* column , for which we ( generically ) assume that most properties are
* null .
*/
if (routine->amcaninclude
&& attno > rd_index->indnkeyatts)
iskey = false ;
switch (prop)
{
case AMPROP_ASC:
if (iskey &&
test_indoption(tuple, attno, routine->amcanorder,
INDOPTION_DESC, 0 , &res))
isnull = false ;
break ;
case AMPROP_DESC:
if (iskey &&
test_indoption(tuple, attno, routine->amcanorder,
INDOPTION_DESC, INDOPTION_DESC, &res))
isnull = false ;
break ;
case AMPROP_NULLS_FIRST:
if (iskey &&
test_indoption(tuple, attno, routine->amcanorder,
INDOPTION_NULLS_FIRST, INDOPTION_NULLS_FIRST, &res))
isnull = false ;
break ;
case AMPROP_NULLS_LAST:
if (iskey &&
test_indoption(tuple, attno, routine->amcanorder,
INDOPTION_NULLS_FIRST, 0 , &res))
isnull = false ;
break ;
case AMPROP_ORDERABLE:
/*
* generic assumption is that nonkey columns are not orderable
*/
res = iskey ? routine->amcanorder : false ;
isnull = false ;
break ;
case AMPROP_DISTANCE_ORDERABLE:
/*
* The conditions for whether a column is distance - orderable
* are really up to the AM ( at time of writing , only GiST
* supports it at all ) . The planner has its own idea based on
* whether it finds an operator with amoppurpose ' o ' , but
* getting there from just the index column type seems like a
* lot of work . So instead we expect the AM to handle this in
* its amproperty routine . The generic result is to return
* false if the AM says it never supports this , or if this is
* a nonkey column , and null otherwise ( meaning we don ' t
* know ) .
*/
if (!iskey || !routine->amcanorderbyop)
{
res = false ;
isnull = false ;
}
break ;
case AMPROP_RETURNABLE:
/* note that we ignore iskey for this property */
isnull = false ;
res = false ;
if (routine->amcanreturn)
{
/*
* If possible , the AM should handle this test in its
* amproperty function without opening the rel . But this
* is the generic fallback if it does not .
*/
Relation indexrel = index_open(index_oid, AccessShareLock);
res = index_can_return(indexrel, attno);
index_close(indexrel, AccessShareLock);
}
break ;
case AMPROP_SEARCH_ARRAY:
if (iskey)
{
res = routine->amsearcharray;
isnull = false ;
}
break ;
case AMPROP_SEARCH_NULLS:
if (iskey)
{
res = routine->amsearchnulls;
isnull = false ;
}
break ;
default :
break ;
}
ReleaseSysCache(tuple);
if (!isnull)
PG_RETURN_BOOL(res);
PG_RETURN_NULL();
}
if (OidIsValid(index_oid))
{
/*
* Handle index - level properties . Currently , these only depend on the
* AM , but that might not be true forever , so we make users name an
* index not just an AM .
*/
switch (prop)
{
case AMPROP_CLUSTERABLE:
PG_RETURN_BOOL(routine->amclusterable);
case AMPROP_INDEX_SCAN:
PG_RETURN_BOOL(routine->amgettuple ? true : false );
case AMPROP_BITMAP_SCAN:
PG_RETURN_BOOL(routine->amgetbitmap ? true : false );
case AMPROP_BACKWARD_SCAN:
PG_RETURN_BOOL(routine->amcanbackward);
default :
PG_RETURN_NULL();
}
}
/*
* Handle AM - level properties ( those that control what you can say in
* CREATE INDEX ) .
*/
switch (prop)
{
case AMPROP_CAN_ORDER:
PG_RETURN_BOOL(routine->amcanorder);
case AMPROP_CAN_UNIQUE:
PG_RETURN_BOOL(routine->amcanunique);
case AMPROP_CAN_MULTI_COL:
PG_RETURN_BOOL(routine->amcanmulticol);
case AMPROP_CAN_EXCLUDE:
PG_RETURN_BOOL(routine->amgettuple ? true : false );
case AMPROP_CAN_INCLUDE:
PG_RETURN_BOOL(routine->amcaninclude);
default :
PG_RETURN_NULL();
}
}
/*
* Test property of an AM specified by AM OID
*/
Datum
pg_indexam_has_property(PG_FUNCTION_ARGS)
{
Oid amoid = PG_GETARG_OID(0 );
char *propname = text_to_cstring(PG_GETARG_TEXT_PP(1 ));
return indexam_property(fcinfo, propname, amoid, InvalidOid, 0 );
}
/*
* Test property of an index specified by index OID
*/
Datum
pg_index_has_property(PG_FUNCTION_ARGS)
{
Oid relid = PG_GETARG_OID(0 );
char *propname = text_to_cstring(PG_GETARG_TEXT_PP(1 ));
return indexam_property(fcinfo, propname, InvalidOid, relid, 0 );
}
/*
* Test property of an index column specified by index OID and column number
*/
Datum
pg_index_column_has_property(PG_FUNCTION_ARGS)
{
Oid relid = PG_GETARG_OID(0 );
int32 attno = PG_GETARG_INT32(1 );
char *propname = text_to_cstring(PG_GETARG_TEXT_PP(2 ));
/* Reject attno 0 immediately, so that attno > 0 identifies this case */
if (attno <= 0 )
PG_RETURN_NULL();
return indexam_property(fcinfo, propname, InvalidOid, relid, attno);
}
/*
* Return the name of the given phase , as used for progress reporting by the
* given AM .
*/
Datum
pg_indexam_progress_phasename(PG_FUNCTION_ARGS)
{
Oid amoid = PG_GETARG_OID(0 );
int32 phasenum = PG_GETARG_INT32(1 );
IndexAmRoutine *routine;
char *name;
routine = GetIndexAmRoutineByAmId(amoid, true );
if (routine == NULL || !routine->ambuildphasename)
PG_RETURN_NULL();
name = routine->ambuildphasename(phasenum);
if (!name)
PG_RETURN_NULL();
PG_RETURN_DATUM(CStringGetTextDatum(name));
}
Messung V0.5 in Prozent C=94 H=94 G=93
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland