From ed0097e4f9e6b1227935e01fa67f12a238b66064 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 13 Aug 2016 18:31:14 -0400 Subject: [PATCH] Add SQL-accessible functions for inspecting index AM properties. Per discussion, we should provide such functions to replace the lost ability to discover AM properties by inspecting pg_am (cf commit 65c5fcd35). The added functionality is also meant to displace any code that was looking directly at pg_index.indoption, since we'd rather not believe that the bit meanings in that field are part of any client API contract. As future-proofing, define the SQL API to not assume that properties that are currently AM-wide or index-wide will remain so unless they logically must be; instead, expose them only when inquiring about a specific index or even specific index column. Also provide the ability for an index AM to override the behavior. In passing, document pg_am.amtype, overlooked in commit 473b93287. Andrew Gierth, with kibitzing by me and others Discussion: <87mvl5on7n.fsf@news-spur.riddles.org.uk> --- contrib/bloom/blutils.c | 1 + doc/src/sgml/catalogs.sgml | 29 +- doc/src/sgml/func.sgml | 165 +++++++++++ doc/src/sgml/indexam.sgml | 50 ++++ src/backend/access/brin/brin.c | 1 + src/backend/access/gin/ginutil.c | 1 + src/backend/access/gist/gist.c | 1 + src/backend/access/gist/gistutil.c | 100 +++++++ src/backend/access/hash/hash.c | 1 + src/backend/access/index/amapi.c | 27 +- src/backend/access/nbtree/nbtree.c | 1 + src/backend/access/nbtree/nbtutils.c | 26 ++ src/backend/access/spgist/spgutils.c | 1 + src/backend/catalog/index.c | 2 +- src/backend/commands/opclasscmds.c | 6 +- src/backend/executor/execAmi.c | 2 +- src/backend/utils/adt/Makefile | 2 +- src/backend/utils/adt/amutils.c | 390 ++++++++++++++++++++++++++ src/include/access/amapi.h | 36 ++- src/include/access/gist_private.h | 3 + src/include/access/nbtree.h | 3 + src/include/catalog/catversion.h | 2 +- src/include/catalog/pg_proc.h | 11 +- src/include/utils/builtins.h | 5 + src/test/regress/expected/amutils.out | 208 ++++++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/serial_schedule | 1 + src/test/regress/sql/amutils.sql | 87 ++++++ 28 files changed, 1146 insertions(+), 18 deletions(-) create mode 100644 src/backend/utils/adt/amutils.c create mode 100644 src/test/regress/expected/amutils.out create mode 100644 src/test/regress/sql/amutils.sql diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c index 28fda0fa56..b5007ed6bb 100644 --- a/contrib/bloom/blutils.c +++ b/contrib/bloom/blutils.c @@ -129,6 +129,7 @@ blhandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; amroutine->amoptions = bloptions; + amroutine->amproperty = NULL; amroutine->amvalidate = blvalidate; amroutine->ambeginscan = blbeginscan; amroutine->amrescan = blrescan; diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index ccb9b97a8c..4e09e06aed 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -529,10 +529,11 @@ - The catalog pg_am stores information about index - access methods. There is one row for each index access method supported by - the system. The requirements for index access methods are discussed in - detail in . + The catalog pg_am stores information about + relation access methods. There is one row for each access method supported + by the system. + Currently, only indexes have access methods. The requirements for index + access methods are discussed in detail in . @@ -573,10 +574,30 @@ + + amtype + char + + + Currently always i to indicate an index access + method; other values may be allowed in future + +
+ + + Before PostgreSQL 9.6, pg_am + contained many additional columns representing properties of index access + methods. That data is now only directly visible at the C code level. + However, pg_index_column_has_property() and related + functions have been added to allow SQL queries to inspect index access + method properties; see . + + + diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 783033403a..426e562b03 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -16289,6 +16289,18 @@ SELECT pg_type_is_visible('myschema.widget'::regtype); pg_get_viewdef + + pg_index_column_has_property + + + + pg_index_has_property + + + + pg_indexam_has_property + + pg_options_to_table @@ -16476,6 +16488,21 @@ SELECT pg_type_is_visible('myschema.widget'::regtype); materialized view; lines with fields are wrapped to specified number of columns, pretty-printing is implied + + pg_index_column_has_property(index_oid, column_no, prop_name) + boolean + test whether an index column has a specified property + + + pg_index_has_property(index_oid, prop_name) + boolean + test whether an index has a specified property + + + pg_indexam_has_property(am_oid, prop_name) + boolean + test whether an index access method has a specified property + pg_options_to_table(reloptions) setof record @@ -16619,6 +16646,144 @@ SELECT pg_type_is_visible('myschema.widget'::regtype); its OID. + + pg_index_column_has_property, + pg_index_has_property, and + pg_indexam_has_property return whether the + specified index column, index, or index access method possesses the named + property. NULL is returned if the property name is not + known or does not apply to the particular object, or if the OID or column + number does not identify a valid object. Refer to + for column properties, + for index properties, and + for access method properties. + (Note that extension access methods can define additional property names + for their indexes.) + + + + Index Column Properties + + + NameDescription + + + + asc + Does the column sort in ascending order on a forward scan? + + + + desc + Does the column sort in descending order on a forward scan? + + + + nulls_first + Does the column sort with nulls first on a forward scan? + + + + nulls_last + Does the column sort with nulls last on a forward scan? + + + + orderable + Does the column possess any defined sort ordering? + + + + distance_orderable + Can the column be scanned in order by a distance + operator, for example ORDER BY col <-> constant ? + + + + returnable + Can the column value be returned by an index-only scan? + + + + search_array + Does the column natively support col = ANY(array) + searches? + + + + search_nulls + Does the column support IS NULL and + IS NOT NULL searches? + + + + +
+ + + Index Properties + + + NameDescription + + + + clusterable + Can the index be used in a CLUSTER command? + + + + index_scan + Does the index support plain (non-bitmap) scans? + + + + bitmap_scan + Does the index support bitmap scans? + + + + backward_scan + Can the index be scanned backwards? + + + + +
+ + + Index Access Method Properties + + + NameDescription + + + + can_order + Does the access method support ASC, + DESC and related keywords in + CREATE INDEX? + + + + can_unique + Does the access method support unique indexes? + + + + can_multi_col + Does the access method support indexes with multiple columns? + + + + can_exclude + Does the access method support exclusion constraints? + + + + +
+ pg_options_to_table returns the set of storage option name/value pairs diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml index fa4842b73f..b59cd0363a 100644 --- a/doc/src/sgml/indexam.sgml +++ b/doc/src/sgml/indexam.sgml @@ -129,6 +129,7 @@ typedef struct IndexAmRoutine amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; amoptions_function amoptions; + amproperty_function amproperty; /* can be NULL */ amvalidate_function amvalidate; ambeginscan_function ambeginscan; amrescan_function amrescan; @@ -407,6 +408,55 @@ amoptions (ArrayType *reloptions, bool +amproperty (Oid index_oid, int attno, + IndexAMProperty prop, const char *propname, + bool *res, bool *isnull); + + The amproperty method allows index access methods to override + the default behavior of pg_index_column_has_property + and related functions. + If the access method does not have any special behavior for index property + inquiries, the amproperty field in + its IndexAmRoutine struct can be set to NULL. + Otherwise, the amproperty method will be called with + index_oid and attno both zero for + pg_indexam_has_property calls, + or with index_oid valid and attno zero for + pg_index_has_property calls, + or with index_oid valid and attno greater than + zero for pg_index_column_has_property calls. + prop is an enum value identifying the property being tested, + while propname is the original property name string. + If the core code does not recognize the property name + then prop is AMPROP_UNKNOWN. + Access methods can define custom property names by + checking propname for a match (use pg_strcasecmp + to match, for consistency with the core code); for names known to the core + code, it's better to inspect prop. + If the amproperty method returns true then + it has determined the property test result: it must set *res + to the boolean value to return, or set *isnull + to true to return a NULL. (Both of the referenced variables + are initialized to false before the call.) + If the amproperty method returns false then + the core code will proceed with its normal logic for determining the + property test result. + + + + Access methods that support ordering operators should + implement AMPROP_DISTANCE_ORDERABLE property testing, as the + core code does not know how to do that and will return NULL. It may + also be advantageous to implement AMPROP_RETURNABLE testing, + if that can be done more cheaply than by opening the index and calling + amcanreturn, which is the core code's default behavior. + The default behavior should be satisfactory for all other standard + properties. + + + + +bool amvalidate (Oid opclassoid); Validate the catalog entries for the specified operator class, so far as diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 89bad0521b..b194d33cc5 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -102,6 +102,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; amroutine->amoptions = brinoptions; + amroutine->amproperty = NULL; amroutine->amvalidate = brinvalidate; amroutine->ambeginscan = brinbeginscan; amroutine->amrescan = brinrescan; diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c index a2450f4687..d9146488c4 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -57,6 +57,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; amroutine->amoptions = ginoptions; + amroutine->amproperty = NULL; amroutine->amvalidate = ginvalidate; amroutine->ambeginscan = ginbeginscan; amroutine->amrescan = ginrescan; diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index e8034b9cd6..9a417ca2f4 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -79,6 +79,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; amroutine->amoptions = gistoptions; + amroutine->amproperty = gistproperty; amroutine->amvalidate = gistvalidate; amroutine->ambeginscan = gistbeginscan; amroutine->amrescan = gistrescan; diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index fac166d4c2..26d4a64694 100644 --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -16,10 +16,13 @@ #include #include "access/gist_private.h" +#include "access/htup_details.h" #include "access/reloptions.h" +#include "catalog/pg_opclass.h" #include "storage/indexfsm.h" #include "storage/lmgr.h" #include "utils/builtins.h" +#include "utils/syscache.h" /* @@ -836,6 +839,103 @@ gistoptions(Datum reloptions, bool validate) return (bytea *) rdopts; } +/* + * gistproperty() -- Check boolean properties of indexes. + * + * This is optional for most AMs, but is required for GiST because the core + * property code doesn't support AMPROP_DISTANCE_ORDERABLE. We also handle + * AMPROP_RETURNABLE here to save opening the rel to call gistcanreturn. + */ +bool +gistproperty(Oid index_oid, int attno, + IndexAMProperty prop, const char *propname, + bool *res, bool *isnull) +{ + HeapTuple tuple; + Form_pg_index rd_index; + Form_pg_opclass rd_opclass; + Datum datum; + bool disnull; + oidvector *indclass; + Oid opclass, + opfamily, + opcintype; + int16 procno; + + /* Only answer column-level inquiries */ + if (attno == 0) + return false; + + /* + * Currently, GiST distance-ordered scans require that there be a distance + * function in the opclass with the default types (i.e. the one loaded + * into the relcache entry, see initGISTstate). So we assume that if such + * a function exists, then there's a reason for it (rather than grubbing + * through all the opfamily's operators to find an ordered one). + * + * Essentially the same code can test whether we support returning the + * column data, since that's true if the opclass provides a fetch proc. + */ + + switch (prop) + { + case AMPROP_DISTANCE_ORDERABLE: + procno = GIST_DISTANCE_PROC; + break; + case AMPROP_RETURNABLE: + procno = GIST_FETCH_PROC; + break; + default: + return false; + } + + /* First we need to know the column's opclass. */ + + tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid)); + if (!HeapTupleIsValid(tuple)) + { + *isnull = true; + return true; + } + rd_index = (Form_pg_index) GETSTRUCT(tuple); + + /* caller is supposed to guarantee this */ + Assert(attno > 0 && attno <= rd_index->indnatts); + + datum = SysCacheGetAttr(INDEXRELID, tuple, + Anum_pg_index_indclass, &disnull); + Assert(!disnull); + + indclass = ((oidvector *) DatumGetPointer(datum)); + opclass = indclass->values[attno - 1]; + + ReleaseSysCache(tuple); + + /* Now look up the opclass family and input datatype. */ + + tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass)); + if (!HeapTupleIsValid(tuple)) + { + *isnull = true; + return true; + } + rd_opclass = (Form_pg_opclass) GETSTRUCT(tuple); + + opfamily = rd_opclass->opcfamily; + opcintype = rd_opclass->opcintype; + + ReleaseSysCache(tuple); + + /* And now we can check whether the function is provided. */ + + *res = SearchSysCacheExists4(AMPROCNUM, + ObjectIdGetDatum(opfamily), + ObjectIdGetDatum(opcintype), + ObjectIdGetDatum(opcintype), + Int16GetDatum(procno)); + return true; +} + /* * Temporary and unlogged GiST indexes are not WAL-logged, but we need LSNs * to detect concurrent page splits anyway. This function provides a fake diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index 30c82e191c..07496f8156 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -75,6 +75,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; amroutine->amoptions = hashoptions; + amroutine->amproperty = NULL; amroutine->amvalidate = hashvalidate; amroutine->ambeginscan = hashbeginscan; amroutine->amrescan = hashrescan; diff --git a/src/backend/access/index/amapi.c b/src/backend/access/index/amapi.c index d347ebcba4..28f6cde896 100644 --- a/src/backend/access/index/amapi.c +++ b/src/backend/access/index/amapi.c @@ -47,9 +47,12 @@ GetIndexAmRoutine(Oid amhandler) /* * GetIndexAmRoutineByAmId - look up the handler of the index access method * with the given OID, and get its IndexAmRoutine struct. + * + * If the given OID isn't a valid index access method, returns NULL if + * noerror is true, else throws error. */ IndexAmRoutine * -GetIndexAmRoutineByAmId(Oid amoid) +GetIndexAmRoutineByAmId(Oid amoid, bool noerror) { HeapTuple tuple; Form_pg_am amform; @@ -58,25 +61,43 @@ GetIndexAmRoutineByAmId(Oid amoid) /* Get handler function OID for the access method */ tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid)); if (!HeapTupleIsValid(tuple)) + { + if (noerror) + return NULL; elog(ERROR, "cache lookup failed for access method %u", amoid); + } amform = (Form_pg_am) GETSTRUCT(tuple); - /* Check if it's index access method */ + /* Check if it's an index access method as opposed to some other AM */ if (amform->amtype != AMTYPE_INDEX) + { + if (noerror) + { + ReleaseSysCache(tuple); + return NULL; + } ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("access method \"%s\" is not of type %s", NameStr(amform->amname), "INDEX"))); + } amhandler = amform->amhandler; /* Complain if handler OID is invalid */ if (!RegProcedureIsValid(amhandler)) + { + if (noerror) + { + ReleaseSysCache(tuple); + return NULL; + } ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("index access method \"%s\" does not have a handler", NameStr(amform->amname)))); + } ReleaseSysCache(tuple); @@ -107,7 +128,7 @@ amvalidate(PG_FUNCTION_ARGS) ReleaseSysCache(classtup); - amroutine = GetIndexAmRoutineByAmId(amoid); + amroutine = GetIndexAmRoutineByAmId(amoid, false); if (amroutine->amvalidate == NULL) elog(ERROR, "function amvalidate is not defined for index access method %u", diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index 1f479735c2..4668c5ee59 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -108,6 +108,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; amroutine->amoptions = btoptions; + amroutine->amproperty = btproperty; amroutine->amvalidate = btvalidate; amroutine->ambeginscan = btbeginscan; amroutine->amrescan = btrescan; diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c index 83c553ca27..5d335c7f97 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -2041,3 +2041,29 @@ btoptions(Datum reloptions, bool validate) { return default_reloptions(reloptions, validate, RELOPT_KIND_BTREE); } + +/* + * btproperty() -- Check boolean properties of indexes. + * + * This is optional, but handling AMPROP_RETURNABLE here saves opening the rel + * to call btcanreturn. + */ +bool +btproperty(Oid index_oid, int attno, + IndexAMProperty prop, const char *propname, + bool *res, bool *isnull) +{ + switch (prop) + { + case AMPROP_RETURNABLE: + /* answer only for columns, not AM or whole index */ + if (attno == 0) + return false; + /* otherwise, btree can always return data */ + *res = true; + return true; + + default: + return false; /* punt to generic code */ + } +} diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c index bc679bf75a..d570ae5992 100644 --- a/src/backend/access/spgist/spgutils.c +++ b/src/backend/access/spgist/spgutils.c @@ -58,6 +58,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; amroutine->amoptions = spgoptions; + amroutine->amproperty = NULL; amroutine->amvalidate = spgvalidate; amroutine->ambeginscan = spgbeginscan; amroutine->amrescan = spgrescan; diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 7b30e469df..b0b43cf02d 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -289,7 +289,7 @@ ConstructTupleDescriptor(Relation heapRelation, int i; /* We need access to the index AM's API struct */ - amroutine = GetIndexAmRoutineByAmId(accessMethodObjectId); + amroutine = GetIndexAmRoutineByAmId(accessMethodObjectId, false); /* ... and to the table's tuple descriptor */ heapTupDesc = RelationGetDescr(heapRelation); diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c index 5f665cf3a2..f4dfdb9642 100644 --- a/src/backend/commands/opclasscmds.c +++ b/src/backend/commands/opclasscmds.c @@ -366,7 +366,7 @@ DefineOpClass(CreateOpClassStmt *stmt) stmt->amname))); amoid = HeapTupleGetOid(tup); - amroutine = GetIndexAmRoutineByAmId(amoid); + amroutine = GetIndexAmRoutineByAmId(amoid, false); ReleaseSysCache(tup); maxOpNumber = amroutine->amstrategies; @@ -791,7 +791,7 @@ AlterOpFamily(AlterOpFamilyStmt *stmt) stmt->amname))); amoid = HeapTupleGetOid(tup); - amroutine = GetIndexAmRoutineByAmId(amoid); + amroutine = GetIndexAmRoutineByAmId(amoid, false); ReleaseSysCache(tup); maxOpNumber = amroutine->amstrategies; @@ -1103,7 +1103,7 @@ assignOperTypes(OpFamilyMember *member, Oid amoid, Oid typeoid) * the family has been created but not yet populated with the required * operators.) */ - IndexAmRoutine *amroutine = GetIndexAmRoutineByAmId(amoid); + IndexAmRoutine *amroutine = GetIndexAmRoutineByAmId(amoid, false); if (!amroutine->amcanorderbyop) ereport(ERROR, diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c index 4a978adea7..2587ef7046 100644 --- a/src/backend/executor/execAmi.c +++ b/src/backend/executor/execAmi.c @@ -555,7 +555,7 @@ IndexSupportsBackwardScan(Oid indexid) idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel); /* Fetch the index AM's API struct */ - amroutine = GetIndexAmRoutineByAmId(idxrelrec->relam); + amroutine = GetIndexAmRoutineByAmId(idxrelrec->relam, false); result = amroutine->amcanbackward; diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 2b4ebc72b4..b9e217ae49 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -9,7 +9,7 @@ top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global # keep this list arranged alphabetically or it gets to be a mess -OBJS = acl.o arrayfuncs.o array_expanded.o array_selfuncs.o \ +OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \ array_typanalyze.o array_userfuncs.o arrayutils.o ascii.o \ bool.o cash.o char.o date.o datetime.o datum.o dbsize.o domains.o \ encode.o enum.o expandeddatum.o \ diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.c new file mode 100644 index 0000000000..ad5e45674b --- /dev/null +++ b/src/backend/utils/adt/amutils.c @@ -0,0 +1,390 @@ +/*------------------------------------------------------------------------- + * + * amutils.c + * SQL-level APIs related to index access methods. + * + * Copyright (c) 2016, 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 + } +}; + +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. + * + * relid/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(Oid relid, int attno, bool guard, + int16 iopt_mask, int16 iopt_expect, + bool *res) +{ + HeapTuple tuple; + Form_pg_index rd_index; + Datum datum; + bool isnull; + int2vector *indoption; + int16 indoption_val; + + if (!guard) + { + *res = false; + return true; + } + + tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tuple)) + return false; + rd_index = (Form_pg_index) GETSTRUCT(tuple); + + Assert(relid == rd_index->indexrelid); + Assert(attno > 0 && attno <= rd_index->indnatts); + + datum = SysCacheGetAttr(INDEXRELID, tuple, + Anum_pg_index_indoption, &isnull); + Assert(!isnull); + + indoption = ((int2vector *) DatumGetPointer(datum)); + indoption_val = indoption->values[attno - 1]; + + *res = (indoption_val & iopt_mask) == iopt_expect; + + ReleaseSysCache(tuple); + + 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) + { + 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, 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) + { + /* Handle column-level properties */ + switch (prop) + { + case AMPROP_ASC: + if (test_indoption(index_oid, attno, routine->amcanorder, + INDOPTION_DESC, 0, &res)) + PG_RETURN_BOOL(res); + PG_RETURN_NULL(); + + case AMPROP_DESC: + if (test_indoption(index_oid, attno, routine->amcanorder, + INDOPTION_DESC, INDOPTION_DESC, &res)) + PG_RETURN_BOOL(res); + PG_RETURN_NULL(); + + case AMPROP_NULLS_FIRST: + if (test_indoption(index_oid, attno, routine->amcanorder, + INDOPTION_NULLS_FIRST, INDOPTION_NULLS_FIRST, &res)) + PG_RETURN_BOOL(res); + PG_RETURN_NULL(); + + case AMPROP_NULLS_LAST: + if (test_indoption(index_oid, attno, routine->amcanorder, + INDOPTION_NULLS_FIRST, 0, &res)) + PG_RETURN_BOOL(res); + PG_RETURN_NULL(); + + case AMPROP_ORDERABLE: + PG_RETURN_BOOL(routine->amcanorder); + + 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, and null + * otherwise (meaning we don't know). + */ + if (!routine->amcanorderbyop) + PG_RETURN_BOOL(false); + PG_RETURN_NULL(); + + case AMPROP_RETURNABLE: + if (!routine->amcanreturn) + PG_RETURN_BOOL(false); + + /* + * 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); + } + + PG_RETURN_BOOL(res); + + case AMPROP_SEARCH_ARRAY: + PG_RETURN_BOOL(routine->amsearcharray); + + case AMPROP_SEARCH_NULLS: + PG_RETURN_BOOL(routine->amsearchnulls); + + default: + 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); + + 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); +} diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 35f1061b3a..1036cca99c 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -26,6 +26,34 @@ struct IndexPath; struct IndexInfo; +/* + * Properties for amproperty API. This list covers properties known to the + * core code, but an index AM can define its own properties, by matching the + * string property name. + */ +typedef enum IndexAMProperty +{ + AMPROP_UNKNOWN = 0, /* anything not known to core code */ + AMPROP_ASC, /* column properties */ + AMPROP_DESC, + AMPROP_NULLS_FIRST, + AMPROP_NULLS_LAST, + AMPROP_ORDERABLE, + AMPROP_DISTANCE_ORDERABLE, + AMPROP_RETURNABLE, + AMPROP_SEARCH_ARRAY, + AMPROP_SEARCH_NULLS, + AMPROP_CLUSTERABLE, /* index properties */ + AMPROP_INDEX_SCAN, + AMPROP_BITMAP_SCAN, + AMPROP_BACKWARD_SCAN, + AMPROP_CAN_ORDER, /* AM properties */ + AMPROP_CAN_UNIQUE, + AMPROP_CAN_MULTI_COL, + AMPROP_CAN_EXCLUDE +} IndexAMProperty; + + /* * Callback function signatures --- see indexam.sgml for more info. */ @@ -72,6 +100,11 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, typedef bytea *(*amoptions_function) (Datum reloptions, bool validate); +/* report AM, index, or index column property */ +typedef bool (*amproperty_function) (Oid index_oid, int attno, + IndexAMProperty prop, const char *propname, + bool *res, bool *isnull); + /* validate definition of an opclass for this AM */ typedef bool (*amvalidate_function) (Oid opclassoid); @@ -154,6 +187,7 @@ typedef struct IndexAmRoutine amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; amoptions_function amoptions; + amproperty_function amproperty; /* can be NULL */ amvalidate_function amvalidate; ambeginscan_function ambeginscan; amrescan_function amrescan; @@ -167,7 +201,7 @@ typedef struct IndexAmRoutine /* Functions in access/index/amapi.c */ extern IndexAmRoutine *GetIndexAmRoutine(Oid amhandler); -extern IndexAmRoutine *GetIndexAmRoutineByAmId(Oid amoid); +extern IndexAmRoutine *GetIndexAmRoutineByAmId(Oid amoid, bool noerror); extern Datum amvalidate(PG_FUNCTION_ARGS); diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h index 23e0fe3197..1231585017 100644 --- a/src/include/access/gist_private.h +++ b/src/include/access/gist_private.h @@ -492,6 +492,9 @@ extern bool gistvalidate(Oid opclassoid); #define GIST_DEFAULT_FILLFACTOR 90 extern bytea *gistoptions(Datum reloptions, bool validate); +extern bool gistproperty(Oid index_oid, int attno, + IndexAMProperty prop, const char *propname, + bool *res, bool *isnull); extern bool gistfitpage(IndexTuple *itvec, int len); extern bool gistnospace(Page page, IndexTuple *itvec, int len, OffsetNumber todelete, Size freespace); extern void gistcheckpage(Relation rel, Buffer buf); diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h index 19437d2863..c580f51f7f 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -748,6 +748,9 @@ extern void _bt_end_vacuum_callback(int code, Datum arg); extern Size BTreeShmemSize(void); extern void BTreeShmemInit(void); extern bytea *btoptions(Datum reloptions, bool validate); +extern bool btproperty(Oid index_oid, int attno, + IndexAMProperty prop, const char *propname, + bool *res, bool *isnull); /* * prototypes for functions in nbtvalidate.c diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 25e26dbbf6..2ca3cd911a 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201608031 +#define CATALOG_VERSION_NO 201608131 #endif diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 270dd213c0..af19c1a82b 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -563,11 +563,18 @@ DATA(insert OID = 334 ( spghandler PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 DESCR("spgist index access method handler"); DATA(insert OID = 335 ( brinhandler PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 325 "2281" _null_ _null_ _null_ _null_ _null_ brinhandler _null_ _null_ _null_ )); DESCR("brin index access method handler"); +DATA(insert OID = 3952 ( brin_summarize_new_values PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 23 "2205" _null_ _null_ _null_ _null_ _null_ brin_summarize_new_values _null_ _null_ _null_ )); +DESCR("brin: standalone scan new table pages"); DATA(insert OID = 338 ( amvalidate PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 16 "26" _null_ _null_ _null_ _null_ _null_ amvalidate _null_ _null_ _null_ )); DESCR("validate an operator class"); -DATA(insert OID = 3952 ( brin_summarize_new_values PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 23 "2205" _null_ _null_ _null_ _null_ _null_ brin_summarize_new_values _null_ _null_ _null_ )); -DESCR("brin: standalone scan new table pages"); + +DATA(insert OID = 636 ( pg_indexam_has_property PGNSP PGUID 12 1 0 0 0 f f f f t f s s 2 0 16 "26 25" _null_ _null_ _null_ _null_ _null_ pg_indexam_has_property _null_ _null_ _null_ )); +DESCR("test property of an index access method"); +DATA(insert OID = 637 ( pg_index_has_property PGNSP PGUID 12 1 0 0 0 f f f f t f s s 2 0 16 "2205 25" _null_ _null_ _null_ _null_ _null_ pg_index_has_property _null_ _null_ _null_ )); +DESCR("test property of an index"); +DATA(insert OID = 638 ( pg_index_column_has_property PGNSP PGUID 12 1 0 0 0 f f f f t f s s 3 0 16 "2205 23 25" _null_ _null_ _null_ _null_ _null_ pg_index_column_has_property _null_ _null_ _null_ )); +DESCR("test property of an index column"); DATA(insert OID = 339 ( poly_same PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "604 604" _null_ _null_ _null_ _null_ _null_ poly_same _null_ _null_ _null_ )); DATA(insert OID = 340 ( poly_contain PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "604 604" _null_ _null_ _null_ _null_ _null_ poly_contain _null_ _null_ _null_ )); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 8cebc861f4..a91be981b9 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -108,6 +108,11 @@ extern Datum pg_has_role_id_id(PG_FUNCTION_ARGS); extern Datum pg_has_role_name(PG_FUNCTION_ARGS); extern Datum pg_has_role_id(PG_FUNCTION_ARGS); +/* amutils.c */ +extern Datum pg_indexam_has_property(PG_FUNCTION_ARGS); +extern Datum pg_index_has_property(PG_FUNCTION_ARGS); +extern Datum pg_index_column_has_property(PG_FUNCTION_ARGS); + /* bool.c */ extern Datum boolin(PG_FUNCTION_ARGS); extern Datum boolout(PG_FUNCTION_ARGS); diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out new file mode 100644 index 0000000000..74f7c9f1fd --- /dev/null +++ b/src/test/regress/expected/amutils.out @@ -0,0 +1,208 @@ +-- +-- Test index AM property-reporting functions +-- +select prop, + pg_indexam_has_property(a.oid, prop) as "AM", + pg_index_has_property('onek_hundred'::regclass, prop) as "Index", + pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as "Column" + from pg_am a, + unnest(array['asc', 'desc', 'nulls_first', 'nulls_last', + 'orderable', 'distance_orderable', 'returnable', + 'search_array', 'search_nulls', + 'clusterable', 'index_scan', 'bitmap_scan', + 'backward_scan', + 'can_order', 'can_unique', 'can_multi_col', + 'can_exclude', + 'bogus']::text[]) + with ordinality as u(prop,ord) + where a.amname = 'btree' + order by ord; + prop | AM | Index | Column +--------------------+----+-------+-------- + asc | | | t + desc | | | f + nulls_first | | | f + nulls_last | | | t + orderable | | | t + distance_orderable | | | f + returnable | | | t + search_array | | | t + search_nulls | | | t + clusterable | | t | + index_scan | | t | + bitmap_scan | | t | + backward_scan | | t | + can_order | t | | + can_unique | t | | + can_multi_col | t | | + can_exclude | t | | + bogus | | | +(18 rows) + +select prop, + pg_indexam_has_property(a.oid, prop) as "AM", + pg_index_has_property('gcircleind'::regclass, prop) as "Index", + pg_index_column_has_property('gcircleind'::regclass, 1, prop) as "Column" + from pg_am a, + unnest(array['asc', 'desc', 'nulls_first', 'nulls_last', + 'orderable', 'distance_orderable', 'returnable', + 'search_array', 'search_nulls', + 'clusterable', 'index_scan', 'bitmap_scan', + 'backward_scan', + 'can_order', 'can_unique', 'can_multi_col', + 'can_exclude', + 'bogus']::text[]) + with ordinality as u(prop,ord) + where a.amname = 'gist' + order by ord; + prop | AM | Index | Column +--------------------+----+-------+-------- + asc | | | f + desc | | | f + nulls_first | | | f + nulls_last | | | f + orderable | | | f + distance_orderable | | | t + returnable | | | f + search_array | | | f + search_nulls | | | t + clusterable | | t | + index_scan | | t | + bitmap_scan | | t | + backward_scan | | f | + can_order | f | | + can_unique | f | | + can_multi_col | t | | + can_exclude | t | | + bogus | | | +(18 rows) + +select prop, + pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as btree, + pg_index_column_has_property('hash_i4_index'::regclass, 1, prop) as hash, + pg_index_column_has_property('gcircleind'::regclass, 1, prop) as gist, + pg_index_column_has_property('sp_radix_ind'::regclass, 1, prop) as spgist, + pg_index_column_has_property('botharrayidx'::regclass, 1, prop) as gin, + pg_index_column_has_property('brinidx'::regclass, 1, prop) as brin + from unnest(array['asc', 'desc', 'nulls_first', 'nulls_last', + 'orderable', 'distance_orderable', 'returnable', + 'search_array', 'search_nulls', + 'bogus']::text[]) + with ordinality as u(prop,ord) + order by ord; + prop | btree | hash | gist | spgist | gin | brin +--------------------+-------+------+------+--------+-----+------ + asc | t | f | f | f | f | f + desc | f | f | f | f | f | f + nulls_first | f | f | f | f | f | f + nulls_last | t | f | f | f | f | f + orderable | t | f | f | f | f | f + distance_orderable | f | f | t | f | f | f + returnable | t | f | f | t | f | f + search_array | t | f | f | f | f | f + search_nulls | t | f | t | t | f | t + bogus | | | | | | +(10 rows) + +select prop, + pg_index_has_property('onek_hundred'::regclass, prop) as btree, + pg_index_has_property('hash_i4_index'::regclass, prop) as hash, + pg_index_has_property('gcircleind'::regclass, prop) as gist, + pg_index_has_property('sp_radix_ind'::regclass, prop) as spgist, + pg_index_has_property('botharrayidx'::regclass, prop) as gin, + pg_index_has_property('brinidx'::regclass, prop) as brin + from unnest(array['clusterable', 'index_scan', 'bitmap_scan', + 'backward_scan', + 'bogus']::text[]) + with ordinality as u(prop,ord) + order by ord; + prop | btree | hash | gist | spgist | gin | brin +---------------+-------+------+------+--------+-----+------ + clusterable | t | f | t | f | f | f + index_scan | t | t | t | t | f | f + bitmap_scan | t | t | t | t | t | t + backward_scan | t | t | f | f | f | f + bogus | | | | | | +(5 rows) + +select amname, prop, pg_indexam_has_property(a.oid, prop) as p + from pg_am a, + unnest(array['can_order', 'can_unique', 'can_multi_col', + 'can_exclude', 'bogus']::text[]) + with ordinality as u(prop,ord) + where amtype = 'i' + order by amname, ord; + amname | prop | p +--------+---------------+--- + brin | can_order | f + brin | can_unique | f + brin | can_multi_col | t + brin | can_exclude | f + brin | bogus | + btree | can_order | t + btree | can_unique | t + btree | can_multi_col | t + btree | can_exclude | t + btree | bogus | + gin | can_order | f + gin | can_unique | f + gin | can_multi_col | t + gin | can_exclude | f + gin | bogus | + gist | can_order | f + gist | can_unique | f + gist | can_multi_col | t + gist | can_exclude | t + gist | bogus | + hash | can_order | f + hash | can_unique | f + hash | can_multi_col | f + hash | can_exclude | t + hash | bogus | + spgist | can_order | f + spgist | can_unique | f + spgist | can_multi_col | f + spgist | can_exclude | t + spgist | bogus | +(30 rows) + +-- +-- additional checks for pg_index_column_has_property +-- +CREATE TEMP TABLE foo (f1 int, f2 int, f3 int, f4 int); +CREATE INDEX fooindex ON foo (f1 desc, f2 asc, f3 nulls first, f4 nulls last); +select col, prop, pg_index_column_has_property(o, col, prop) + from (values ('fooindex'::regclass)) v1(o), + (values (1,'orderable'),(2,'asc'),(3,'desc'), + (4,'nulls_first'),(5,'nulls_last'), + (6, 'bogus')) v2(idx,prop), + generate_series(1,4) col + order by col, idx; + col | prop | pg_index_column_has_property +-----+-------------+------------------------------ + 1 | orderable | t + 1 | asc | f + 1 | desc | t + 1 | nulls_first | t + 1 | nulls_last | f + 1 | bogus | + 2 | orderable | t + 2 | asc | t + 2 | desc | f + 2 | nulls_first | f + 2 | nulls_last | t + 2 | bogus | + 3 | orderable | t + 3 | asc | t + 3 | desc | f + 3 | nulls_first | t + 3 | nulls_last | f + 3 | bogus | + 4 | orderable | t + 4 | asc | t + 4 | desc | f + 4 | nulls_first | f + 4 | nulls_last | t + 4 | bogus | +(24 rows) + diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 4ebad04d06..3815182fe7 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -92,7 +92,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview test: alter_generic alter_operator misc psql async dbsize misc_functions # rules cannot run concurrently with any test that creates a view -test: rules psql_crosstab select_parallel +test: rules psql_crosstab select_parallel amutils # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 5c7038d6e1..8958d8cdb9 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -126,6 +126,7 @@ test: misc_functions test: rules test: psql_crosstab test: select_parallel +test: amutils test: select_views test: portals_p2 test: foreign_key diff --git a/src/test/regress/sql/amutils.sql b/src/test/regress/sql/amutils.sql new file mode 100644 index 0000000000..cec1dcb53b --- /dev/null +++ b/src/test/regress/sql/amutils.sql @@ -0,0 +1,87 @@ +-- +-- Test index AM property-reporting functions +-- + +select prop, + pg_indexam_has_property(a.oid, prop) as "AM", + pg_index_has_property('onek_hundred'::regclass, prop) as "Index", + pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as "Column" + from pg_am a, + unnest(array['asc', 'desc', 'nulls_first', 'nulls_last', + 'orderable', 'distance_orderable', 'returnable', + 'search_array', 'search_nulls', + 'clusterable', 'index_scan', 'bitmap_scan', + 'backward_scan', + 'can_order', 'can_unique', 'can_multi_col', + 'can_exclude', + 'bogus']::text[]) + with ordinality as u(prop,ord) + where a.amname = 'btree' + order by ord; + +select prop, + pg_indexam_has_property(a.oid, prop) as "AM", + pg_index_has_property('gcircleind'::regclass, prop) as "Index", + pg_index_column_has_property('gcircleind'::regclass, 1, prop) as "Column" + from pg_am a, + unnest(array['asc', 'desc', 'nulls_first', 'nulls_last', + 'orderable', 'distance_orderable', 'returnable', + 'search_array', 'search_nulls', + 'clusterable', 'index_scan', 'bitmap_scan', + 'backward_scan', + 'can_order', 'can_unique', 'can_multi_col', + 'can_exclude', + 'bogus']::text[]) + with ordinality as u(prop,ord) + where a.amname = 'gist' + order by ord; + +select prop, + pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as btree, + pg_index_column_has_property('hash_i4_index'::regclass, 1, prop) as hash, + pg_index_column_has_property('gcircleind'::regclass, 1, prop) as gist, + pg_index_column_has_property('sp_radix_ind'::regclass, 1, prop) as spgist, + pg_index_column_has_property('botharrayidx'::regclass, 1, prop) as gin, + pg_index_column_has_property('brinidx'::regclass, 1, prop) as brin + from unnest(array['asc', 'desc', 'nulls_first', 'nulls_last', + 'orderable', 'distance_orderable', 'returnable', + 'search_array', 'search_nulls', + 'bogus']::text[]) + with ordinality as u(prop,ord) + order by ord; + +select prop, + pg_index_has_property('onek_hundred'::regclass, prop) as btree, + pg_index_has_property('hash_i4_index'::regclass, prop) as hash, + pg_index_has_property('gcircleind'::regclass, prop) as gist, + pg_index_has_property('sp_radix_ind'::regclass, prop) as spgist, + pg_index_has_property('botharrayidx'::regclass, prop) as gin, + pg_index_has_property('brinidx'::regclass, prop) as brin + from unnest(array['clusterable', 'index_scan', 'bitmap_scan', + 'backward_scan', + 'bogus']::text[]) + with ordinality as u(prop,ord) + order by ord; + +select amname, prop, pg_indexam_has_property(a.oid, prop) as p + from pg_am a, + unnest(array['can_order', 'can_unique', 'can_multi_col', + 'can_exclude', 'bogus']::text[]) + with ordinality as u(prop,ord) + where amtype = 'i' + order by amname, ord; + +-- +-- additional checks for pg_index_column_has_property +-- +CREATE TEMP TABLE foo (f1 int, f2 int, f3 int, f4 int); + +CREATE INDEX fooindex ON foo (f1 desc, f2 asc, f3 nulls first, f4 nulls last); + +select col, prop, pg_index_column_has_property(o, col, prop) + from (values ('fooindex'::regclass)) v1(o), + (values (1,'orderable'),(2,'asc'),(3,'desc'), + (4,'nulls_first'),(5,'nulls_last'), + (6, 'bogus')) v2(idx,prop), + generate_series(1,4) col + order by col, idx;