mirror of
https://github.com/postgres/postgres.git
synced 2025-11-12 05:01:15 +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 commit65c5fcd35). 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 commit473b93287. Andrew Gierth, with kibitzing by me and others Discussion: <87mvl5on7n.fsf@news-spur.riddles.org.uk>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user