1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-31 22:04:40 +03:00

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>
This commit is contained in:
Tom Lane
2016-08-13 18:31:14 -04:00
parent 4997878193
commit ed0097e4f9
28 changed files with 1146 additions and 18 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -16,10 +16,13 @@
#include <math.h>
#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

View File

@ -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;

View File

@ -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",

View File

@ -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;

View File

@ -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 */
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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,

View File

@ -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;

View File

@ -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 \

View File

@ -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);
}

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201608031
#define CATALOG_VERSION_NO 201608131
#endif

View File

@ -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_ ));

View File

@ -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);

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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;