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:
parent
4997878193
commit
ed0097e4f9
@ -129,6 +129,7 @@ blhandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amcanreturn = NULL;
|
amroutine->amcanreturn = NULL;
|
||||||
amroutine->amcostestimate = blcostestimate;
|
amroutine->amcostestimate = blcostestimate;
|
||||||
amroutine->amoptions = bloptions;
|
amroutine->amoptions = bloptions;
|
||||||
|
amroutine->amproperty = NULL;
|
||||||
amroutine->amvalidate = blvalidate;
|
amroutine->amvalidate = blvalidate;
|
||||||
amroutine->ambeginscan = blbeginscan;
|
amroutine->ambeginscan = blbeginscan;
|
||||||
amroutine->amrescan = blrescan;
|
amroutine->amrescan = blrescan;
|
||||||
|
@ -529,10 +529,11 @@
|
|||||||
</indexterm>
|
</indexterm>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
The catalog <structname>pg_am</structname> stores information about index
|
The catalog <structname>pg_am</structname> stores information about
|
||||||
access methods. There is one row for each index access method supported by
|
relation access methods. There is one row for each access method supported
|
||||||
the system. The requirements for index access methods are discussed in
|
by the system.
|
||||||
detail in <xref linkend="indexam">.
|
Currently, only indexes have access methods. The requirements for index
|
||||||
|
access methods are discussed in detail in <xref linkend="indexam">.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
@ -573,10 +574,30 @@
|
|||||||
</entry>
|
</entry>
|
||||||
</row>
|
</row>
|
||||||
|
|
||||||
|
<row>
|
||||||
|
<entry><structfield>amtype</structfield></entry>
|
||||||
|
<entry><type>char</type></entry>
|
||||||
|
<entry></entry>
|
||||||
|
<entry>
|
||||||
|
Currently always <literal>i</literal> to indicate an index access
|
||||||
|
method; other values may be allowed in future
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
</tbody>
|
</tbody>
|
||||||
</tgroup>
|
</tgroup>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<note>
|
||||||
|
<para>
|
||||||
|
Before <productname>PostgreSQL</> 9.6, <structname>pg_am</structname>
|
||||||
|
contained many additional columns representing properties of index access
|
||||||
|
methods. That data is now only directly visible at the C code level.
|
||||||
|
However, <function>pg_index_column_has_property()</function> and related
|
||||||
|
functions have been added to allow SQL queries to inspect index access
|
||||||
|
method properties; see <xref linkend="functions-info-catalog-table">.
|
||||||
|
</para>
|
||||||
|
</note>
|
||||||
|
|
||||||
</sect1>
|
</sect1>
|
||||||
|
|
||||||
|
|
||||||
|
@ -16289,6 +16289,18 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
|
|||||||
<primary>pg_get_viewdef</primary>
|
<primary>pg_get_viewdef</primary>
|
||||||
</indexterm>
|
</indexterm>
|
||||||
|
|
||||||
|
<indexterm>
|
||||||
|
<primary>pg_index_column_has_property</primary>
|
||||||
|
</indexterm>
|
||||||
|
|
||||||
|
<indexterm>
|
||||||
|
<primary>pg_index_has_property</primary>
|
||||||
|
</indexterm>
|
||||||
|
|
||||||
|
<indexterm>
|
||||||
|
<primary>pg_indexam_has_property</primary>
|
||||||
|
</indexterm>
|
||||||
|
|
||||||
<indexterm>
|
<indexterm>
|
||||||
<primary>pg_options_to_table</primary>
|
<primary>pg_options_to_table</primary>
|
||||||
</indexterm>
|
</indexterm>
|
||||||
@ -16476,6 +16488,21 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
|
|||||||
materialized view; lines with fields are wrapped to specified
|
materialized view; lines with fields are wrapped to specified
|
||||||
number of columns, pretty-printing is implied</entry>
|
number of columns, pretty-printing is implied</entry>
|
||||||
</row>
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal><function>pg_index_column_has_property(<parameter>index_oid</parameter>, <parameter>column_no</>, <parameter>prop_name</>)</function></literal></entry>
|
||||||
|
<entry><type>boolean</type></entry>
|
||||||
|
<entry>test whether an index column has a specified property</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal><function>pg_index_has_property(<parameter>index_oid</parameter>, <parameter>prop_name</>)</function></literal></entry>
|
||||||
|
<entry><type>boolean</type></entry>
|
||||||
|
<entry>test whether an index has a specified property</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal><function>pg_indexam_has_property(<parameter>am_oid</parameter>, <parameter>prop_name</>)</function></literal></entry>
|
||||||
|
<entry><type>boolean</type></entry>
|
||||||
|
<entry>test whether an index access method has a specified property</entry>
|
||||||
|
</row>
|
||||||
<row>
|
<row>
|
||||||
<entry><literal><function>pg_options_to_table(<parameter>reloptions</parameter>)</function></literal></entry>
|
<entry><literal><function>pg_options_to_table(<parameter>reloptions</parameter>)</function></literal></entry>
|
||||||
<entry><type>setof record</type></entry>
|
<entry><type>setof record</type></entry>
|
||||||
@ -16619,6 +16646,144 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
|
|||||||
its OID.
|
its OID.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<function>pg_index_column_has_property</function>,
|
||||||
|
<function>pg_index_has_property</function>, and
|
||||||
|
<function>pg_indexam_has_property</function> return whether the
|
||||||
|
specified index column, index, or index access method possesses the named
|
||||||
|
property. <literal>NULL</literal> 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
|
||||||
|
<xref linkend="functions-info-index-column-props"> for column properties,
|
||||||
|
<xref linkend="functions-info-index-props"> for index properties, and
|
||||||
|
<xref linkend="functions-info-indexam-props"> for access method properties.
|
||||||
|
(Note that extension access methods can define additional property names
|
||||||
|
for their indexes.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<table id="functions-info-index-column-props">
|
||||||
|
<title>Index Column Properties</title>
|
||||||
|
<tgroup cols="2">
|
||||||
|
<thead>
|
||||||
|
<row><entry>Name</entry><entry>Description</entry></row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry><literal>asc</literal></entry>
|
||||||
|
<entry>Does the column sort in ascending order on a forward scan?
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>desc</literal></entry>
|
||||||
|
<entry>Does the column sort in descending order on a forward scan?
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>nulls_first</literal></entry>
|
||||||
|
<entry>Does the column sort with nulls first on a forward scan?
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>nulls_last</literal></entry>
|
||||||
|
<entry>Does the column sort with nulls last on a forward scan?
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>orderable</literal></entry>
|
||||||
|
<entry>Does the column possess any defined sort ordering?
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>distance_orderable</literal></entry>
|
||||||
|
<entry>Can the column be scanned in order by a <quote>distance</>
|
||||||
|
operator, for example <literal>ORDER BY col <-> constant</> ?
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>returnable</literal></entry>
|
||||||
|
<entry>Can the column value be returned by an index-only scan?
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>search_array</literal></entry>
|
||||||
|
<entry>Does the column natively support <literal>col = ANY(array)</>
|
||||||
|
searches?
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>search_nulls</literal></entry>
|
||||||
|
<entry>Does the column support <literal>IS NULL</> and
|
||||||
|
<literal>IS NOT NULL</> searches?
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table id="functions-info-index-props">
|
||||||
|
<title>Index Properties</title>
|
||||||
|
<tgroup cols="2">
|
||||||
|
<thead>
|
||||||
|
<row><entry>Name</entry><entry>Description</entry></row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry><literal>clusterable</literal></entry>
|
||||||
|
<entry>Can the index be used in a <literal>CLUSTER</> command?
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>index_scan</literal></entry>
|
||||||
|
<entry>Does the index support plain (non-bitmap) scans?
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>bitmap_scan</literal></entry>
|
||||||
|
<entry>Does the index support bitmap scans?
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>backward_scan</literal></entry>
|
||||||
|
<entry>Can the index be scanned backwards?
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table id="functions-info-indexam-props">
|
||||||
|
<title>Index Access Method Properties</title>
|
||||||
|
<tgroup cols="2">
|
||||||
|
<thead>
|
||||||
|
<row><entry>Name</entry><entry>Description</entry></row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry><literal>can_order</literal></entry>
|
||||||
|
<entry>Does the access method support <literal>ASC</>,
|
||||||
|
<literal>DESC</> and related keywords in
|
||||||
|
<literal>CREATE INDEX</>?
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>can_unique</literal></entry>
|
||||||
|
<entry>Does the access method support unique indexes?
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>can_multi_col</literal></entry>
|
||||||
|
<entry>Does the access method support indexes with multiple columns?
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>can_exclude</literal></entry>
|
||||||
|
<entry>Does the access method support exclusion constraints?
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</table>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
<function>pg_options_to_table</function> returns the set of storage
|
<function>pg_options_to_table</function> returns the set of storage
|
||||||
option name/value pairs
|
option name/value pairs
|
||||||
|
@ -129,6 +129,7 @@ typedef struct IndexAmRoutine
|
|||||||
amcanreturn_function amcanreturn; /* can be NULL */
|
amcanreturn_function amcanreturn; /* can be NULL */
|
||||||
amcostestimate_function amcostestimate;
|
amcostestimate_function amcostestimate;
|
||||||
amoptions_function amoptions;
|
amoptions_function amoptions;
|
||||||
|
amproperty_function amproperty; /* can be NULL */
|
||||||
amvalidate_function amvalidate;
|
amvalidate_function amvalidate;
|
||||||
ambeginscan_function ambeginscan;
|
ambeginscan_function ambeginscan;
|
||||||
amrescan_function amrescan;
|
amrescan_function amrescan;
|
||||||
@ -407,6 +408,55 @@ amoptions (ArrayType *reloptions,
|
|||||||
<para>
|
<para>
|
||||||
<programlisting>
|
<programlisting>
|
||||||
bool
|
bool
|
||||||
|
amproperty (Oid index_oid, int attno,
|
||||||
|
IndexAMProperty prop, const char *propname,
|
||||||
|
bool *res, bool *isnull);
|
||||||
|
</programlisting>
|
||||||
|
The <function>amproperty</> method allows index access methods to override
|
||||||
|
the default behavior of <function>pg_index_column_has_property</function>
|
||||||
|
and related functions.
|
||||||
|
If the access method does not have any special behavior for index property
|
||||||
|
inquiries, the <structfield>amproperty</> field in
|
||||||
|
its <structname>IndexAmRoutine</> struct can be set to NULL.
|
||||||
|
Otherwise, the <function>amproperty</> method will be called with
|
||||||
|
<parameter>index_oid</> and <parameter>attno</> both zero for
|
||||||
|
<function>pg_indexam_has_property</function> calls,
|
||||||
|
or with <parameter>index_oid</> valid and <parameter>attno</> zero for
|
||||||
|
<function>pg_index_has_property</function> calls,
|
||||||
|
or with <parameter>index_oid</> valid and <parameter>attno</> greater than
|
||||||
|
zero for <function>pg_index_column_has_property</function> calls.
|
||||||
|
<parameter>prop</> is an enum value identifying the property being tested,
|
||||||
|
while <parameter>propname</> is the original property name string.
|
||||||
|
If the core code does not recognize the property name
|
||||||
|
then <parameter>prop</> is <literal>AMPROP_UNKNOWN</>.
|
||||||
|
Access methods can define custom property names by
|
||||||
|
checking <parameter>propname</> for a match (use <function>pg_strcasecmp</>
|
||||||
|
to match, for consistency with the core code); for names known to the core
|
||||||
|
code, it's better to inspect <parameter>prop</>.
|
||||||
|
If the <structfield>amproperty</> method returns <literal>true</> then
|
||||||
|
it has determined the property test result: it must set <literal>*res</>
|
||||||
|
to the boolean value to return, or set <literal>*isnull</>
|
||||||
|
to <literal>true</> to return a NULL. (Both of the referenced variables
|
||||||
|
are initialized to <literal>false</> before the call.)
|
||||||
|
If the <structfield>amproperty</> method returns <literal>false</> then
|
||||||
|
the core code will proceed with its normal logic for determining the
|
||||||
|
property test result.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Access methods that support ordering operators should
|
||||||
|
implement <literal>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 <literal>AMPROP_RETURNABLE</> testing,
|
||||||
|
if that can be done more cheaply than by opening the index and calling
|
||||||
|
<structfield>amcanreturn</>, which is the core code's default behavior.
|
||||||
|
The default behavior should be satisfactory for all other standard
|
||||||
|
properties.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<programlisting>
|
||||||
|
bool
|
||||||
amvalidate (Oid opclassoid);
|
amvalidate (Oid opclassoid);
|
||||||
</programlisting>
|
</programlisting>
|
||||||
Validate the catalog entries for the specified operator class, so far as
|
Validate the catalog entries for the specified operator class, so far as
|
||||||
|
@ -102,6 +102,7 @@ brinhandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amcanreturn = NULL;
|
amroutine->amcanreturn = NULL;
|
||||||
amroutine->amcostestimate = brincostestimate;
|
amroutine->amcostestimate = brincostestimate;
|
||||||
amroutine->amoptions = brinoptions;
|
amroutine->amoptions = brinoptions;
|
||||||
|
amroutine->amproperty = NULL;
|
||||||
amroutine->amvalidate = brinvalidate;
|
amroutine->amvalidate = brinvalidate;
|
||||||
amroutine->ambeginscan = brinbeginscan;
|
amroutine->ambeginscan = brinbeginscan;
|
||||||
amroutine->amrescan = brinrescan;
|
amroutine->amrescan = brinrescan;
|
||||||
|
@ -57,6 +57,7 @@ ginhandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amcanreturn = NULL;
|
amroutine->amcanreturn = NULL;
|
||||||
amroutine->amcostestimate = gincostestimate;
|
amroutine->amcostestimate = gincostestimate;
|
||||||
amroutine->amoptions = ginoptions;
|
amroutine->amoptions = ginoptions;
|
||||||
|
amroutine->amproperty = NULL;
|
||||||
amroutine->amvalidate = ginvalidate;
|
amroutine->amvalidate = ginvalidate;
|
||||||
amroutine->ambeginscan = ginbeginscan;
|
amroutine->ambeginscan = ginbeginscan;
|
||||||
amroutine->amrescan = ginrescan;
|
amroutine->amrescan = ginrescan;
|
||||||
|
@ -79,6 +79,7 @@ gisthandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amcanreturn = gistcanreturn;
|
amroutine->amcanreturn = gistcanreturn;
|
||||||
amroutine->amcostestimate = gistcostestimate;
|
amroutine->amcostestimate = gistcostestimate;
|
||||||
amroutine->amoptions = gistoptions;
|
amroutine->amoptions = gistoptions;
|
||||||
|
amroutine->amproperty = gistproperty;
|
||||||
amroutine->amvalidate = gistvalidate;
|
amroutine->amvalidate = gistvalidate;
|
||||||
amroutine->ambeginscan = gistbeginscan;
|
amroutine->ambeginscan = gistbeginscan;
|
||||||
amroutine->amrescan = gistrescan;
|
amroutine->amrescan = gistrescan;
|
||||||
|
@ -16,10 +16,13 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include "access/gist_private.h"
|
#include "access/gist_private.h"
|
||||||
|
#include "access/htup_details.h"
|
||||||
#include "access/reloptions.h"
|
#include "access/reloptions.h"
|
||||||
|
#include "catalog/pg_opclass.h"
|
||||||
#include "storage/indexfsm.h"
|
#include "storage/indexfsm.h"
|
||||||
#include "storage/lmgr.h"
|
#include "storage/lmgr.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
|
#include "utils/syscache.h"
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -836,6 +839,103 @@ gistoptions(Datum reloptions, bool validate)
|
|||||||
return (bytea *) rdopts;
|
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
|
* Temporary and unlogged GiST indexes are not WAL-logged, but we need LSNs
|
||||||
* to detect concurrent page splits anyway. This function provides a fake
|
* to detect concurrent page splits anyway. This function provides a fake
|
||||||
|
@ -75,6 +75,7 @@ hashhandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amcanreturn = NULL;
|
amroutine->amcanreturn = NULL;
|
||||||
amroutine->amcostestimate = hashcostestimate;
|
amroutine->amcostestimate = hashcostestimate;
|
||||||
amroutine->amoptions = hashoptions;
|
amroutine->amoptions = hashoptions;
|
||||||
|
amroutine->amproperty = NULL;
|
||||||
amroutine->amvalidate = hashvalidate;
|
amroutine->amvalidate = hashvalidate;
|
||||||
amroutine->ambeginscan = hashbeginscan;
|
amroutine->ambeginscan = hashbeginscan;
|
||||||
amroutine->amrescan = hashrescan;
|
amroutine->amrescan = hashrescan;
|
||||||
|
@ -47,9 +47,12 @@ GetIndexAmRoutine(Oid amhandler)
|
|||||||
/*
|
/*
|
||||||
* GetIndexAmRoutineByAmId - look up the handler of the index access method
|
* GetIndexAmRoutineByAmId - look up the handler of the index access method
|
||||||
* with the given OID, and get its IndexAmRoutine struct.
|
* 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 *
|
IndexAmRoutine *
|
||||||
GetIndexAmRoutineByAmId(Oid amoid)
|
GetIndexAmRoutineByAmId(Oid amoid, bool noerror)
|
||||||
{
|
{
|
||||||
HeapTuple tuple;
|
HeapTuple tuple;
|
||||||
Form_pg_am amform;
|
Form_pg_am amform;
|
||||||
@ -58,25 +61,43 @@ GetIndexAmRoutineByAmId(Oid amoid)
|
|||||||
/* Get handler function OID for the access method */
|
/* Get handler function OID for the access method */
|
||||||
tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid));
|
tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid));
|
||||||
if (!HeapTupleIsValid(tuple))
|
if (!HeapTupleIsValid(tuple))
|
||||||
|
{
|
||||||
|
if (noerror)
|
||||||
|
return NULL;
|
||||||
elog(ERROR, "cache lookup failed for access method %u",
|
elog(ERROR, "cache lookup failed for access method %u",
|
||||||
amoid);
|
amoid);
|
||||||
|
}
|
||||||
amform = (Form_pg_am) GETSTRUCT(tuple);
|
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 (amform->amtype != AMTYPE_INDEX)
|
||||||
|
{
|
||||||
|
if (noerror)
|
||||||
|
{
|
||||||
|
ReleaseSysCache(tuple);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||||
errmsg("access method \"%s\" is not of type %s",
|
errmsg("access method \"%s\" is not of type %s",
|
||||||
NameStr(amform->amname), "INDEX")));
|
NameStr(amform->amname), "INDEX")));
|
||||||
|
}
|
||||||
|
|
||||||
amhandler = amform->amhandler;
|
amhandler = amform->amhandler;
|
||||||
|
|
||||||
/* Complain if handler OID is invalid */
|
/* Complain if handler OID is invalid */
|
||||||
if (!RegProcedureIsValid(amhandler))
|
if (!RegProcedureIsValid(amhandler))
|
||||||
|
{
|
||||||
|
if (noerror)
|
||||||
|
{
|
||||||
|
ReleaseSysCache(tuple);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||||
errmsg("index access method \"%s\" does not have a handler",
|
errmsg("index access method \"%s\" does not have a handler",
|
||||||
NameStr(amform->amname))));
|
NameStr(amform->amname))));
|
||||||
|
}
|
||||||
|
|
||||||
ReleaseSysCache(tuple);
|
ReleaseSysCache(tuple);
|
||||||
|
|
||||||
@ -107,7 +128,7 @@ amvalidate(PG_FUNCTION_ARGS)
|
|||||||
|
|
||||||
ReleaseSysCache(classtup);
|
ReleaseSysCache(classtup);
|
||||||
|
|
||||||
amroutine = GetIndexAmRoutineByAmId(amoid);
|
amroutine = GetIndexAmRoutineByAmId(amoid, false);
|
||||||
|
|
||||||
if (amroutine->amvalidate == NULL)
|
if (amroutine->amvalidate == NULL)
|
||||||
elog(ERROR, "function amvalidate is not defined for index access method %u",
|
elog(ERROR, "function amvalidate is not defined for index access method %u",
|
||||||
|
@ -108,6 +108,7 @@ bthandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amcanreturn = btcanreturn;
|
amroutine->amcanreturn = btcanreturn;
|
||||||
amroutine->amcostestimate = btcostestimate;
|
amroutine->amcostestimate = btcostestimate;
|
||||||
amroutine->amoptions = btoptions;
|
amroutine->amoptions = btoptions;
|
||||||
|
amroutine->amproperty = btproperty;
|
||||||
amroutine->amvalidate = btvalidate;
|
amroutine->amvalidate = btvalidate;
|
||||||
amroutine->ambeginscan = btbeginscan;
|
amroutine->ambeginscan = btbeginscan;
|
||||||
amroutine->amrescan = btrescan;
|
amroutine->amrescan = btrescan;
|
||||||
|
@ -2041,3 +2041,29 @@ btoptions(Datum reloptions, bool validate)
|
|||||||
{
|
{
|
||||||
return default_reloptions(reloptions, validate, RELOPT_KIND_BTREE);
|
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->amcanreturn = spgcanreturn;
|
||||||
amroutine->amcostestimate = spgcostestimate;
|
amroutine->amcostestimate = spgcostestimate;
|
||||||
amroutine->amoptions = spgoptions;
|
amroutine->amoptions = spgoptions;
|
||||||
|
amroutine->amproperty = NULL;
|
||||||
amroutine->amvalidate = spgvalidate;
|
amroutine->amvalidate = spgvalidate;
|
||||||
amroutine->ambeginscan = spgbeginscan;
|
amroutine->ambeginscan = spgbeginscan;
|
||||||
amroutine->amrescan = spgrescan;
|
amroutine->amrescan = spgrescan;
|
||||||
|
@ -289,7 +289,7 @@ ConstructTupleDescriptor(Relation heapRelation,
|
|||||||
int i;
|
int i;
|
||||||
|
|
||||||
/* We need access to the index AM's API struct */
|
/* We need access to the index AM's API struct */
|
||||||
amroutine = GetIndexAmRoutineByAmId(accessMethodObjectId);
|
amroutine = GetIndexAmRoutineByAmId(accessMethodObjectId, false);
|
||||||
|
|
||||||
/* ... and to the table's tuple descriptor */
|
/* ... and to the table's tuple descriptor */
|
||||||
heapTupDesc = RelationGetDescr(heapRelation);
|
heapTupDesc = RelationGetDescr(heapRelation);
|
||||||
|
@ -366,7 +366,7 @@ DefineOpClass(CreateOpClassStmt *stmt)
|
|||||||
stmt->amname)));
|
stmt->amname)));
|
||||||
|
|
||||||
amoid = HeapTupleGetOid(tup);
|
amoid = HeapTupleGetOid(tup);
|
||||||
amroutine = GetIndexAmRoutineByAmId(amoid);
|
amroutine = GetIndexAmRoutineByAmId(amoid, false);
|
||||||
ReleaseSysCache(tup);
|
ReleaseSysCache(tup);
|
||||||
|
|
||||||
maxOpNumber = amroutine->amstrategies;
|
maxOpNumber = amroutine->amstrategies;
|
||||||
@ -791,7 +791,7 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
|
|||||||
stmt->amname)));
|
stmt->amname)));
|
||||||
|
|
||||||
amoid = HeapTupleGetOid(tup);
|
amoid = HeapTupleGetOid(tup);
|
||||||
amroutine = GetIndexAmRoutineByAmId(amoid);
|
amroutine = GetIndexAmRoutineByAmId(amoid, false);
|
||||||
ReleaseSysCache(tup);
|
ReleaseSysCache(tup);
|
||||||
|
|
||||||
maxOpNumber = amroutine->amstrategies;
|
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
|
* the family has been created but not yet populated with the required
|
||||||
* operators.)
|
* operators.)
|
||||||
*/
|
*/
|
||||||
IndexAmRoutine *amroutine = GetIndexAmRoutineByAmId(amoid);
|
IndexAmRoutine *amroutine = GetIndexAmRoutineByAmId(amoid, false);
|
||||||
|
|
||||||
if (!amroutine->amcanorderbyop)
|
if (!amroutine->amcanorderbyop)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
|
@ -555,7 +555,7 @@ IndexSupportsBackwardScan(Oid indexid)
|
|||||||
idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
|
idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
|
||||||
|
|
||||||
/* Fetch the index AM's API struct */
|
/* Fetch the index AM's API struct */
|
||||||
amroutine = GetIndexAmRoutineByAmId(idxrelrec->relam);
|
amroutine = GetIndexAmRoutineByAmId(idxrelrec->relam, false);
|
||||||
|
|
||||||
result = amroutine->amcanbackward;
|
result = amroutine->amcanbackward;
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ top_builddir = ../../../..
|
|||||||
include $(top_builddir)/src/Makefile.global
|
include $(top_builddir)/src/Makefile.global
|
||||||
|
|
||||||
# keep this list arranged alphabetically or it gets to be a mess
|
# 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 \
|
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 \
|
bool.o cash.o char.o date.o datetime.o datum.o dbsize.o domains.o \
|
||||||
encode.o enum.o expandeddatum.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);
|
||||||
|
}
|
@ -26,6 +26,34 @@ struct IndexPath;
|
|||||||
struct IndexInfo;
|
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.
|
* 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,
|
typedef bytea *(*amoptions_function) (Datum reloptions,
|
||||||
bool validate);
|
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 */
|
/* validate definition of an opclass for this AM */
|
||||||
typedef bool (*amvalidate_function) (Oid opclassoid);
|
typedef bool (*amvalidate_function) (Oid opclassoid);
|
||||||
|
|
||||||
@ -154,6 +187,7 @@ typedef struct IndexAmRoutine
|
|||||||
amcanreturn_function amcanreturn; /* can be NULL */
|
amcanreturn_function amcanreturn; /* can be NULL */
|
||||||
amcostestimate_function amcostestimate;
|
amcostestimate_function amcostestimate;
|
||||||
amoptions_function amoptions;
|
amoptions_function amoptions;
|
||||||
|
amproperty_function amproperty; /* can be NULL */
|
||||||
amvalidate_function amvalidate;
|
amvalidate_function amvalidate;
|
||||||
ambeginscan_function ambeginscan;
|
ambeginscan_function ambeginscan;
|
||||||
amrescan_function amrescan;
|
amrescan_function amrescan;
|
||||||
@ -167,7 +201,7 @@ typedef struct IndexAmRoutine
|
|||||||
|
|
||||||
/* Functions in access/index/amapi.c */
|
/* Functions in access/index/amapi.c */
|
||||||
extern IndexAmRoutine *GetIndexAmRoutine(Oid amhandler);
|
extern IndexAmRoutine *GetIndexAmRoutine(Oid amhandler);
|
||||||
extern IndexAmRoutine *GetIndexAmRoutineByAmId(Oid amoid);
|
extern IndexAmRoutine *GetIndexAmRoutineByAmId(Oid amoid, bool noerror);
|
||||||
|
|
||||||
extern Datum amvalidate(PG_FUNCTION_ARGS);
|
extern Datum amvalidate(PG_FUNCTION_ARGS);
|
||||||
|
|
||||||
|
@ -492,6 +492,9 @@ extern bool gistvalidate(Oid opclassoid);
|
|||||||
#define GIST_DEFAULT_FILLFACTOR 90
|
#define GIST_DEFAULT_FILLFACTOR 90
|
||||||
|
|
||||||
extern bytea *gistoptions(Datum reloptions, bool validate);
|
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 gistfitpage(IndexTuple *itvec, int len);
|
||||||
extern bool gistnospace(Page page, IndexTuple *itvec, int len, OffsetNumber todelete, Size freespace);
|
extern bool gistnospace(Page page, IndexTuple *itvec, int len, OffsetNumber todelete, Size freespace);
|
||||||
extern void gistcheckpage(Relation rel, Buffer buf);
|
extern void gistcheckpage(Relation rel, Buffer buf);
|
||||||
|
@ -748,6 +748,9 @@ extern void _bt_end_vacuum_callback(int code, Datum arg);
|
|||||||
extern Size BTreeShmemSize(void);
|
extern Size BTreeShmemSize(void);
|
||||||
extern void BTreeShmemInit(void);
|
extern void BTreeShmemInit(void);
|
||||||
extern bytea *btoptions(Datum reloptions, bool validate);
|
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
|
* prototypes for functions in nbtvalidate.c
|
||||||
|
@ -53,6 +53,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* yyyymmddN */
|
/* yyyymmddN */
|
||||||
#define CATALOG_VERSION_NO 201608031
|
#define CATALOG_VERSION_NO 201608131
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -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");
|
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_ ));
|
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");
|
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_ ));
|
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");
|
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 = 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_ ));
|
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_ ));
|
||||||
|
@ -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_name(PG_FUNCTION_ARGS);
|
||||||
extern Datum pg_has_role_id(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 */
|
/* bool.c */
|
||||||
extern Datum boolin(PG_FUNCTION_ARGS);
|
extern Datum boolin(PG_FUNCTION_ARGS);
|
||||||
extern Datum boolout(PG_FUNCTION_ARGS);
|
extern Datum boolout(PG_FUNCTION_ARGS);
|
||||||
|
208
src/test/regress/expected/amutils.out
Normal file
208
src/test/regress/expected/amutils.out
Normal 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)
|
||||||
|
|
@ -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
|
test: alter_generic alter_operator misc psql async dbsize misc_functions
|
||||||
|
|
||||||
# rules cannot run concurrently with any test that creates a view
|
# 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
|
# Another group of parallel tests
|
||||||
|
@ -126,6 +126,7 @@ test: misc_functions
|
|||||||
test: rules
|
test: rules
|
||||||
test: psql_crosstab
|
test: psql_crosstab
|
||||||
test: select_parallel
|
test: select_parallel
|
||||||
|
test: amutils
|
||||||
test: select_views
|
test: select_views
|
||||||
test: portals_p2
|
test: portals_p2
|
||||||
test: foreign_key
|
test: foreign_key
|
||||||
|
87
src/test/regress/sql/amutils.sql
Normal file
87
src/test/regress/sql/amutils.sql
Normal 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;
|
Loading…
x
Reference in New Issue
Block a user