mirror of
https://github.com/postgres/postgres.git
synced 2025-08-18 12:22:09 +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;
|
||||
|
@@ -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);
|
||||
|
@@ -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,
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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 \
|
||||
|
390
src/backend/utils/adt/amutils.c
Normal file
390
src/backend/utils/adt/amutils.c
Normal 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);
|
||||
}
|
Reference in New Issue
Block a user