1
0
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 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;