| /*------------------------------------------------------------------------- |
| * |
| * amutils.c |
| * SQL-level APIs related to index access methods. |
| * |
| * Copyright (c) 2016-2023, 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)); |
| } |