Support CREATE ACCESS METHOD

This enables external code to create access methods.  This is useful so
that extensions can add their own access methods which can be formally
tracked for dependencies, so that DROP operates correctly.  Also, having
explicit support makes pg_dump work correctly.

Currently only index AMs are supported, but we expect different types to
be added in the future.

Authors: Alexander Korotkov, Petr Jelínek
Reviewed-By: Teodor Sigaev, Petr Jelínek, Jim Nasby
Commitfest-URL: https://commitfest.postgresql.org/9/353/
Discussion: https://www.postgresql.org/message-id/CAPpHfdsXwZmojm6Dx+TJnpYk27kT4o7Ri6X_4OSWcByu1Rm+VA@mail.gmail.com
This commit is contained in:
Alvaro Herrera 2016-03-23 23:01:35 -03:00
parent 2c6af4f442
commit 473b932870
37 changed files with 1150 additions and 101 deletions

View File

@ -57,6 +57,12 @@
to insert an appropriate row for themselves.
</para>
<para>
Index access access methods can be defined and dropped using
<xref linkend="sql-create-access-method"> and
<xref linkend="sql-drop-access-method"> SQL commands respectively.
</para>
<para>
An index access method handler function must be declared to accept a
single argument of type <type>internal</> and to return the

View File

@ -52,6 +52,7 @@ Complete list of usable sgml source files in this directory.
<!ENTITY commit SYSTEM "commit.sgml">
<!ENTITY commitPrepared SYSTEM "commit_prepared.sgml">
<!ENTITY copyTable SYSTEM "copy.sgml">
<!ENTITY createAccessMethod SYSTEM "create_access_method.sgml">
<!ENTITY createAggregate SYSTEM "create_aggregate.sgml">
<!ENTITY createCast SYSTEM "create_cast.sgml">
<!ENTITY createCollation SYSTEM "create_collation.sgml">
@ -94,6 +95,7 @@ Complete list of usable sgml source files in this directory.
<!ENTITY delete SYSTEM "delete.sgml">
<!ENTITY discard SYSTEM "discard.sgml">
<!ENTITY do SYSTEM "do.sgml">
<!ENTITY dropAccessMethod SYSTEM "drop_access_method.sgml">
<!ENTITY dropAggregate SYSTEM "drop_aggregate.sgml">
<!ENTITY dropCast SYSTEM "drop_cast.sgml">
<!ENTITY dropCollation SYSTEM "drop_collation.sgml">

View File

@ -0,0 +1,120 @@
<!--
doc/src/sgml/ref/create_access_method.sgml
PostgreSQL documentation
-->
<refentry id="sql-create-access-method">
<indexterm zone="sql-create-access-method">
<primary>CREATE ACCESS METHOD</primary>
</indexterm>
<refmeta>
<refentrytitle>CREATE ACCESS METHOD</refentrytitle>
<manvolnum>7</manvolnum>
<refmiscinfo>SQL - Language Statements</refmiscinfo>
</refmeta>
<refnamediv>
<refname>CREATE ACCESS METHOD</refname>
<refpurpose>define a new access method</refpurpose>
</refnamediv>
<refsynopsisdiv>
<synopsis>
CREATE ACCESS METHOD <replaceable class="parameter">name</replaceable>
TYPE <replaceable class="parameter">access_method_type</replaceable>
HANDLER <replaceable class="parameter">handler_function</replaceable>
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<command>CREATE ACCESS METHOD</command> creates a new access method.
</para>
<para>
The access method name must be unique within the database.
</para>
<para>
Only superusers can define new access methods.
</para>
</refsect1>
<refsect1>
<title>Parameters</title>
<variablelist>
<varlistentry>
<term><replaceable class="parameter">name</replaceable></term>
<listitem>
<para>
The name of the access method to be created.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>access_method_type</literal></term>
<listitem>
<para>
This clause specifies type of access method to define.
Only <literal>INDEX</literal> is supported at present.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>HANDLER <replaceable class="parameter">handler_function</replaceable></literal></term>
<listitem>
<para><replaceable class="parameter">handler_function</replaceable> is the
name of a previously registered function that will be called to
retrieve the struct which contains required parameters and functions
of access method to the core. The handler function must take single
argument of type <type>internal</>, and its return type depends on the
type of access method; for <literal>INDEX</literal> access methods, it
must be <type>index_am_handler</type>.
</para>
<para>
See <xref linkend="index-api"> for index access methods API.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Examples</title>
<para>
Create an access method <literal>heptree</> with
handler function <literal>heptree_handler</>:
<programlisting>
CREATE ACCESS METHOD heptree TYPE INDEX HANDLER heptree_handler;
</programlisting>
</para>
</refsect1>
<refsect1>
<title>Compatibility</title>
<para>
<command>CREATE ACCESS METHOD</command> is a
<productname>PostgreSQL</> extension.
</para>
</refsect1>
<refsect1>
<title>See Also</title>
<simplelist type="inline">
<member><xref linkend="sql-drop-access-method"></member>
<member><xref linkend="sql-createopclass"></member>
<member><xref linkend="sql-createopfamily"></member>
</simplelist>
</refsect1>
</refentry>

View File

@ -0,0 +1,112 @@
<!--
doc/src/sgml/ref/drop_access_method.sgml
PostgreSQL documentation
-->
<refentry id="sql-drop-access-method">
<indexterm zone="sql-drop-access-method">
<primary>DROP ACCESS METHOD</primary>
</indexterm>
<refmeta>
<refentrytitle>DROP ACCESS METHOD</refentrytitle>
<manvolnum>7</manvolnum>
<refmiscinfo>SQL - Language Statements</refmiscinfo>
</refmeta>
<refnamediv>
<refname>DROP ACCESS METHOD</refname>
<refpurpose>remove an access method</refpurpose>
</refnamediv>
<refsynopsisdiv>
<synopsis>
DROP ACCESS METHOD [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ CASCADE | RESTRICT ]
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<command>DROP ACCESS METHOD</command> removes an existing access method.
Only superusers can drop access methods.
</para>
<para>
</para>
</refsect1>
<refsect1>
<title>Parameters</title>
<variablelist>
<varlistentry>
<term><literal>IF EXISTS</literal></term>
<listitem>
<para>
Do not throw an error if the access method does not exist.
A notice is issued in this case.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">name</replaceable></term>
<listitem>
<para>
The name of an existing access method.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
<para>
Automatically drop objects that depend on the access method
(such as operator classes, operator families, indexes).
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>RESTRICT</literal></term>
<listitem>
<para>
Refuse to drop the access method if any objects depend on it.
This is the default.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Examples</title>
<para>
Drop the access method <literal>heptree</>:
<programlisting>
DROP ACCESS METHOD heptree;
</programlisting></para>
</refsect1>
<refsect1>
<title>Compatibility</title>
<para>
<command>DROP ACCESS METHOD</command> is a
<productname>PostgreSQL</> extension.
</para>
</refsect1>
<refsect1>
<title>See Also</title>
<simplelist type="inline">
<member><xref linkend="sql-create-access-method"></member>
</simplelist>
</refsect1>
</refentry>

View File

@ -80,6 +80,7 @@
&commit;
&commitPrepared;
&copyTable;
&createAccessMethod;
&createAggregate;
&createCast;
&createCollation;
@ -122,6 +123,7 @@
&delete;
&discard;
&do;
&dropAccessMethod;
&dropAggregate;
&dropCast;
&dropCollation;

View File

@ -62,6 +62,13 @@ GetIndexAmRoutineByAmId(Oid amoid)
amoid);
amform = (Form_pg_am) GETSTRUCT(tuple);
/* Check if it's index access method */
if (amform->amtype != AMTYPE_INDEX)
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 */

View File

@ -20,6 +20,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@ -141,6 +142,7 @@ static const Oid object_classes[] = {
OperatorRelationId, /* OCLASS_OPERATOR */
OperatorClassRelationId, /* OCLASS_OPCLASS */
OperatorFamilyRelationId, /* OCLASS_OPFAMILY */
AccessMethodRelationId, /* OCLASS_AM */
AccessMethodOperatorRelationId, /* OCLASS_AMOP */
AccessMethodProcedureRelationId, /* OCLASS_AMPROC */
RewriteRelationId, /* OCLASS_REWRITE */
@ -1199,6 +1201,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemoveOpFamilyById(object->objectId);
break;
case OCLASS_AM:
RemoveAccessMethodById(object->objectId);
break;
case OCLASS_AMOP:
RemoveAmOpEntryById(object->objectId);
break;
@ -2356,6 +2362,9 @@ getObjectClass(const ObjectAddress *object)
case OperatorFamilyRelationId:
return OCLASS_OPFAMILY;
case AccessMethodRelationId:
return OCLASS_AM;
case AccessMethodOperatorRelationId:
return OCLASS_AMOP;

View File

@ -109,6 +109,18 @@ typedef struct
static const ObjectPropertyType ObjectProperty[] =
{
{
AccessMethodRelationId,
AmOidIndexId,
AMOID,
AMNAME,
Anum_pg_am_amname,
InvalidAttrNumber,
InvalidAttrNumber,
InvalidAttrNumber,
-1,
true
},
{
CastRelationId,
CastOidIndexId,
@ -561,6 +573,10 @@ static const struct object_type_map
{
"operator family", OBJECT_OPFAMILY
},
/* OCLASS_AM */
{
"access method", OBJECT_ACCESS_METHOD
},
/* OCLASS_AMOP */
{
"operator of access method", OBJECT_AMOP
@ -795,6 +811,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
case OBJECT_FDW:
case OBJECT_FOREIGN_SERVER:
case OBJECT_EVENT_TRIGGER:
case OBJECT_ACCESS_METHOD:
address = get_object_address_unqualified(objtype,
objname, missing_ok);
break;
@ -1019,6 +1036,9 @@ get_object_address_unqualified(ObjectType objtype,
switch (objtype)
{
case OBJECT_ACCESS_METHOD:
msg = gettext_noop("access method name cannot be qualified");
break;
case OBJECT_DATABASE:
msg = gettext_noop("database name cannot be qualified");
break;
@ -1061,6 +1081,11 @@ get_object_address_unqualified(ObjectType objtype,
/* Translate name to OID. */
switch (objtype)
{
case OBJECT_ACCESS_METHOD:
address.classId = AccessMethodRelationId;
address.objectId = get_am_oid(name, missing_ok);
address.objectSubId = 0;
break;
case OBJECT_DATABASE:
address.classId = DatabaseRelationId;
address.objectId = get_database_oid(name, missing_ok);
@ -1489,7 +1514,7 @@ get_object_address_opcf(ObjectType objtype, List *objname, bool missing_ok)
ObjectAddress address;
/* XXX no missing_ok support here */
amoid = get_am_oid(strVal(linitial(objname)), false);
amoid = get_index_am_oid(strVal(linitial(objname)), false);
objname = list_copy_tail(objname, 1);
switch (objtype)
@ -2179,6 +2204,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
break;
case OBJECT_TSPARSER:
case OBJECT_TSTEMPLATE:
case OBJECT_ACCESS_METHOD:
/* We treat these object types as being owned by superusers */
if (!superuser_arg(roleid))
ereport(ERROR,
@ -3129,6 +3155,21 @@ getObjectDescription(const ObjectAddress *object)
break;
}
case OCLASS_AM:
{
HeapTuple tup;
tup = SearchSysCache1(AMOID,
ObjectIdGetDatum(object->objectId));
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for access method %u",
object->objectId);
appendStringInfo(&buffer, _("access method %s"),
NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
ReleaseSysCache(tup);
break;
}
default:
appendStringInfo(&buffer, "unrecognized object %u %u %d",
object->classId,
@ -3610,6 +3651,10 @@ getObjectTypeDescription(const ObjectAddress *object)
appendStringInfoString(&buffer, "transform");
break;
case OCLASS_AM:
appendStringInfoString(&buffer, "access method");
break;
default:
appendStringInfo(&buffer, "unrecognized %u", object->classId);
break;
@ -4566,6 +4611,20 @@ getObjectIdentityParts(const ObjectAddress *object,
}
break;
case OCLASS_AM:
{
char *amname;
amname = get_am_name(object->objectId);
if (!amname)
elog(ERROR, "cache lookup failed for access method %u",
object->objectId);
appendStringInfoString(&buffer, quote_identifier(amname));
if (objname)
*objname = list_make1(amname);
}
break;
default:
appendStringInfo(&buffer, "unrecognized object %u %u %d",
object->classId,

View File

@ -12,7 +12,7 @@ subdir = src/backend/commands
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
OBJS = amcmds.o aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
dbcommands.o define.o discard.o dropcmds.o \
event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \

View File

@ -0,0 +1,271 @@
/*-------------------------------------------------------------------------
*
* amcmds.c
* Routines for SQL commands that manipulate access methods.
*
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/commands/amcmds.c
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/heapam.h"
#include "access/htup_details.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_am.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "miscadmin.h"
#include "parser/parse_func.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
static Oid lookup_index_am_handler_func(List *handler_name, char amtype);
static char *get_am_type_string(char amtype);
/*
* CreateAcessMethod
* Registers a new access method.
*/
ObjectAddress
CreateAccessMethod(CreateAmStmt *stmt)
{
Relation rel;
ObjectAddress myself;
ObjectAddress referenced;
Oid amoid;
Oid amhandler;
bool nulls[Natts_pg_am];
Datum values[Natts_pg_am];
HeapTuple tup;
rel = heap_open(AccessMethodRelationId, RowExclusiveLock);
/* Must be super user */
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to create access method \"%s\"",
stmt->amname),
errhint("Must be superuser to create an access method.")));
/* Check if name is used */
amoid = GetSysCacheOid1(AMNAME, CStringGetDatum(stmt->amname));
if (OidIsValid(amoid))
{
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("access method \"%s\" already exists",
stmt->amname)));
}
/*
* Get the handler function oid, verifying the AM type while at it.
*/
amhandler = lookup_index_am_handler_func(stmt->handler_name, stmt->amtype);
/*
* Insert tuple into pg_am.
*/
memset(values, 0, sizeof(values));
memset(nulls, false, sizeof(nulls));
values[Anum_pg_am_amname - 1] =
DirectFunctionCall1(namein, CStringGetDatum(stmt->amname));
values[Anum_pg_am_amhandler - 1] = ObjectIdGetDatum(amhandler);
values[Anum_pg_am_amtype - 1] = CharGetDatum(stmt->amtype);
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
amoid = simple_heap_insert(rel, tup);
CatalogUpdateIndexes(rel, tup);
heap_freetuple(tup);
myself.classId = AccessMethodRelationId;
myself.objectId = amoid;
myself.objectSubId = 0;
/* Record dependency on handler function */
referenced.classId = ProcedureRelationId;
referenced.objectId = amhandler;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
recordDependencyOnCurrentExtension(&myself, false);
heap_close(rel, RowExclusiveLock);
return myself;
}
/*
* Guts of access method deletion.
*/
void
RemoveAccessMethodById(Oid amOid)
{
Relation relation;
HeapTuple tup;
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to drop access methods")));
relation = heap_open(AccessMethodRelationId, RowExclusiveLock);
tup = SearchSysCache1(AMOID, ObjectIdGetDatum(amOid));
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for access method %u", amOid);
simple_heap_delete(relation, &tup->t_self);
ReleaseSysCache(tup);
heap_close(relation, RowExclusiveLock);
}
/*
* get_am_type_oid
* Worker for various get_am_*_oid variants
*
* If missing_ok is false, throw an error if access method not found. If
* true, just return InvalidOid.
*
* If amtype is not '\0', an error is raised if the AM found is not of the
* given type.
*/
static Oid
get_am_type_oid(const char *amname, char amtype, bool missing_ok)
{
HeapTuple tup;
Oid oid = InvalidOid;
tup = SearchSysCache1(AMNAME, CStringGetDatum(amname));
if (HeapTupleIsValid(tup))
{
Form_pg_am amform = (Form_pg_am) GETSTRUCT(tup);
if (amtype != '\0' &&
amform->amtype != amtype)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("access method \"%s\" is not of type %s",
NameStr(amform->amname),
get_am_type_string(amtype))));
oid = HeapTupleGetOid(tup);
ReleaseSysCache(tup);
}
if (!OidIsValid(oid) && !missing_ok)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("access method \"%s\" does not exist", amname)));
return oid;
}
/*
* get_index_am_oid - given an access method name, look up its OID
* and verify it corresponds to an index AM.
*/
Oid
get_index_am_oid(const char *amname, bool missing_ok)
{
return get_am_type_oid(amname, AMTYPE_INDEX, missing_ok);
}
/*
* get_am_oid - given an access method name, look up its OID.
* The type is not checked.
*/
Oid
get_am_oid(const char *amname, bool missing_ok)
{
return get_am_type_oid(amname, '\0', missing_ok);
}
/*
* get_am_name - given an access method OID name and type, look up its name.
*/
char *
get_am_name(Oid amOid)
{
HeapTuple tup;
char *result = NULL;
tup = SearchSysCache1(AMOID, ObjectIdGetDatum(amOid));
if (HeapTupleIsValid(tup))
{
Form_pg_am amform = (Form_pg_am) GETSTRUCT(tup);
result = pstrdup(NameStr(amform->amname));
ReleaseSysCache(tup);
}
return result;
}
/*
* Convert single charater access method type into string for error reporting.
*/
static char *
get_am_type_string(char amtype)
{
switch (amtype)
{
case AMTYPE_INDEX:
return "INDEX";
default:
/* shouldn't happen */
elog(ERROR, "invalid access method type '%c'", amtype);
}
}
/*
* Convert a handler function name to an Oid. If the return type of the
* function doesn't match the given AM type, an error is raised.
*
* This function either return valid function Oid or throw an error.
*/
static Oid
lookup_index_am_handler_func(List *handler_name, char amtype)
{
Oid handlerOid;
static const Oid funcargtypes[1] = {INTERNALOID};
if (handler_name == NIL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("handler function is not specified")));
/* handlers have one argument of type internal */
handlerOid = LookupFuncName(handler_name, 1, funcargtypes, false);
/* check that handler has the correct return type */
switch (amtype)
{
case AMTYPE_INDEX:
if (get_func_rettype(handlerOid) != INDEX_AM_HANDLEROID)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("function %s must return type \"%s\"",
NameListToString(handler_name),
"index_am_handler")));
break;
default:
elog(ERROR, "unrecognized access method type \"%c\"", amtype);
}
return handlerOid;
}

View File

@ -86,6 +86,7 @@ typedef enum
/* XXX merge this with ObjectTypeMap? */
static event_trigger_support_data event_trigger_support[] = {
{"ACCESS METHOD", true},
{"AGGREGATE", true},
{"CAST", true},
{"CONSTRAINT", true},
@ -1078,6 +1079,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_EVENT_TRIGGER:
/* no support for event triggers on event triggers */
return false;
case OBJECT_ACCESS_METHOD:
case OBJECT_AGGREGATE:
case OBJECT_AMOP:
case OBJECT_AMPROC:
@ -1167,6 +1169,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_DEFACL:
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_AM:
return true;
}

View File

@ -678,6 +678,12 @@ DefineOpClass(CreateOpClassStmt *stmt)
myself.objectId = opclassoid;
myself.objectSubId = 0;
/* dependency on access method */
referenced.classId = AccessMethodRelationId;
referenced.objectId = amoid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
/* dependency on namespace */
referenced.classId = NamespaceRelationId;
referenced.objectId = namespaceoid;
@ -743,7 +749,7 @@ DefineOpFamily(CreateOpFamilyStmt *stmt)
get_namespace_name(namespaceoid));
/* Get access method OID, throwing an error if it doesn't exist. */
amoid = get_am_oid(stmt->amname, false);
amoid = get_index_am_oid(stmt->amname, false);
/* XXX Should we make any privilege check against the AM? */
@ -1663,21 +1669,6 @@ RemoveAmProcEntryById(Oid entryOid)
heap_close(rel, RowExclusiveLock);
}
char *
get_am_name(Oid amOid)
{
HeapTuple tup;
char *result = NULL;
tup = SearchSysCache1(AMOID, ObjectIdGetDatum(amOid));
if (HeapTupleIsValid(tup))
{
result = pstrdup(NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
ReleaseSysCache(tup);
}
return result;
}
/*
* Subroutine for ALTER OPERATOR CLASS SET SCHEMA/RENAME
*
@ -1723,22 +1714,3 @@ IsThereOpFamilyInNamespace(const char *opfname, Oid opfmethod,
get_am_name(opfmethod),
get_namespace_name(opfnamespace))));
}
/*
* get_am_oid - given an access method name, look up the OID
*
* If missing_ok is false, throw an error if access method not found. If
* true, just return InvalidOid.
*/
Oid
get_am_oid(const char *amname, bool missing_ok)
{
Oid oid;
oid = GetSysCacheOid1(AMNAME, CStringGetDatum(amname));
if (!OidIsValid(oid) && !missing_ok)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("access method \"%s\" does not exist", amname)));
return oid;
}

View File

@ -3831,6 +3831,18 @@ _copyCreateTransformStmt(const CreateTransformStmt *from)
return newnode;
}
static CreateAmStmt *
_copyCreateAmStmt(const CreateAmStmt *from)
{
CreateAmStmt *newnode = makeNode(CreateAmStmt);
COPY_STRING_FIELD(amname);
COPY_NODE_FIELD(handler_name);
COPY_SCALAR_FIELD(amtype);
return newnode;
}
static CreateTrigStmt *
_copyCreateTrigStmt(const CreateTrigStmt *from)
{
@ -4822,6 +4834,9 @@ copyObject(const void *from)
case T_CreateTransformStmt:
retval = _copyCreateTransformStmt(from);
break;
case T_CreateAmStmt:
retval = _copyCreateAmStmt(from);
break;
case T_CreateTrigStmt:
retval = _copyCreateTrigStmt(from);
break;

View File

@ -1855,6 +1855,16 @@ _equalCreateTransformStmt(const CreateTransformStmt *a, const CreateTransformStm
return true;
}
static bool
_equalCreateAmStmt(const CreateAmStmt *a, const CreateAmStmt *b)
{
COMPARE_STRING_FIELD(amname);
COMPARE_NODE_FIELD(handler_name);
COMPARE_SCALAR_FIELD(amtype);
return true;
}
static bool
_equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
{
@ -3147,6 +3157,9 @@ equal(const void *a, const void *b)
case T_CreateTransformStmt:
retval = _equalCreateTransformStmt(a, b);
break;
case T_CreateAmStmt:
retval = _equalCreateAmStmt(a, b);
break;
case T_CreateTrigStmt:
retval = _equalCreateTrigStmt(a, b);
break;

View File

@ -51,6 +51,7 @@
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/pg_am.h"
#include "catalog/pg_trigger.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
@ -263,7 +264,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DeallocateStmt PrepareStmt ExecuteStmt
DropOwnedStmt ReassignOwnedStmt
AlterTSConfigurationStmt AlterTSDictionaryStmt
CreateMatViewStmt RefreshMatViewStmt
CreateMatViewStmt RefreshMatViewStmt CreateAmStmt
%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
@ -604,7 +605,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NONE
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
@ -789,6 +790,7 @@ stmt :
| CommentStmt
| ConstraintsSetStmt
| CopyStmt
| CreateAmStmt
| CreateAsStmt
| CreateAssertStmt
| CreateCastStmt
@ -4706,6 +4708,23 @@ row_security_cmd:
| DELETE_P { $$ = "delete"; }
;
/*****************************************************************************
*
* QUERY:
* CREATE ACCESS METHOD name HANDLER handler_name
*
*****************************************************************************/
CreateAmStmt: CREATE ACCESS METHOD name TYPE_P INDEX HANDLER handler_name
{
CreateAmStmt *n = makeNode(CreateAmStmt);
n->amname = $4;
n->handler_name = $8;
n->amtype = AMTYPE_INDEX;
$$ = (Node *) n;
}
;
/*****************************************************************************
*
* QUERIES :
@ -5612,6 +5631,7 @@ drop_type: TABLE { $$ = OBJECT_TABLE; }
| MATERIALIZED VIEW { $$ = OBJECT_MATVIEW; }
| INDEX { $$ = OBJECT_INDEX; }
| FOREIGN TABLE { $$ = OBJECT_FOREIGN_TABLE; }
| ACCESS METHOD { $$ = OBJECT_ACCESS_METHOD; }
| EVENT TRIGGER { $$ = OBJECT_EVENT_TRIGGER; }
| COLLATION { $$ = OBJECT_COLLATION; }
| CONVERSION_P { $$ = OBJECT_CONVERSION; }
@ -13778,6 +13798,7 @@ unreserved_keyword:
| MATCH
| MATERIALIZED
| MAXVALUE
| METHOD
| MINUTE_P
| MINVALUE
| MODE

View File

@ -1709,7 +1709,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
* else dump and reload will produce a different index (breaking
* pg_upgrade in particular).
*/
if (index_rel->rd_rel->relam != get_am_oid(DEFAULT_INDEX_TYPE, false))
if (index_rel->rd_rel->relam != get_index_am_oid(DEFAULT_INDEX_TYPE, false))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("index \"%s\" is not a btree", index_name),

View File

@ -1520,6 +1520,10 @@ ProcessUtilitySlow(Node *parsetree,
address = ExecSecLabelStmt((SecLabelStmt *) parsetree);
break;
case T_CreateAmStmt:
address = CreateAccessMethod((CreateAmStmt *) parsetree);
break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(parsetree));
@ -2160,6 +2164,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_TRANSFORM:
tag = "DROP TRANSFORM";
break;
case OBJECT_ACCESS_METHOD:
tag = "DROP ACCESS METHOD";
break;
default:
tag = "???";
}
@ -2256,6 +2263,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_COLLATION:
tag = "CREATE COLLATION";
break;
case OBJECT_ACCESS_METHOD:
tag = "CREATE ACCESS METHOD";
break;
default:
tag = "???";
}
@ -2519,6 +2529,10 @@ CreateCommandTag(Node *parsetree)
tag = "ALTER POLICY";
break;
case T_CreateAmStmt:
tag = "CREATE ACCESS METHOD";
break;
case T_PrepareStmt:
tag = "PREPARE";
break;
@ -3076,6 +3090,10 @@ GetCommandLogLevel(Node *parsetree)
lev = LOGSTMT_DDL;
break;
case T_CreateAmStmt:
lev = LOGSTMT_DDL;
break;
/* already-planned queries */
case T_PlannedStmt:
{

View File

@ -6013,21 +6013,7 @@ string_to_bytea_const(const char *str, size_t str_len)
*-------------------------------------------------------------------------
*/
/*
* deconstruct_indexquals is a simple function to examine the indexquals
* attached to a proposed IndexPath. It returns a list of IndexQualInfo
* structs, one per qual expression.
*/
typedef struct
{
RestrictInfo *rinfo; /* the indexqual itself */
int indexcol; /* zero-based index column number */
bool varonleft; /* true if index column is on left of qual */
Oid clause_op; /* qual's operator OID, if relevant */
Node *other_operand; /* non-index operand of qual's operator */
} IndexQualInfo;
static List *
List *
deconstruct_indexquals(IndexPath *path)
{
List *result = NIL;
@ -6177,35 +6163,7 @@ orderby_operands_eval_cost(PlannerInfo *root, IndexPath *path)
return qual_arg_cost;
}
/*
* genericcostestimate is a general-purpose estimator that can be used for
* most index types. In some cases we use genericcostestimate as the base
* code and then incorporate additional index-type-specific knowledge in
* the type-specific calling function. To avoid code duplication, we make
* genericcostestimate return a number of intermediate values as well as
* its preliminary estimates of the output cost values. The GenericCosts
* struct includes all these values.
*
* Callers should initialize all fields of GenericCosts to zero. In addition,
* they can set numIndexTuples to some positive value if they have a better
* than default way of estimating the number of leaf index tuples visited.
*/
typedef struct
{
/* These are the values the cost estimator must return to the planner */
Cost indexStartupCost; /* index-related startup cost */
Cost indexTotalCost; /* total index-related scan cost */
Selectivity indexSelectivity; /* selectivity of index */
double indexCorrelation; /* order correlation of index */
/* Intermediate values we obtain along the way */
double numIndexPages; /* number of leaf pages visited */
double numIndexTuples; /* number of leaf tuples visited */
double spc_random_page_cost; /* relevant random_page_cost value */
double num_sa_scans; /* # indexscans from ScalarArrayOps */
} GenericCosts;
static void
void
genericcostestimate(PlannerInfo *root,
IndexPath *path,
double loop_count,

View File

@ -98,6 +98,7 @@ getSchemaData(Archive *fout, int *numTablesPtr)
int numProcLangs;
int numCasts;
int numTransforms;
int numAccessMethods;
int numOpclasses;
int numOpfamilies;
int numConversions;
@ -168,6 +169,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
oprinfo = getOperators(fout, &numOperators);
oprinfoindex = buildIndexArray(oprinfo, numOperators, sizeof(OprInfo));
if (g_verbose)
write_msg(NULL, "reading user-defined access methods\n");
getAccessMethods(fout, &numAccessMethods);
if (g_verbose)
write_msg(NULL, "reading user-defined operator classes\n");
getOpclasses(fout, &numOpclasses);

View File

@ -45,6 +45,7 @@
#include "access/attnum.h"
#include "access/sysattr.h"
#include "access/transam.h"
#include "catalog/pg_am.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_class.h"
#include "catalog/pg_default_acl.h"
@ -173,6 +174,7 @@ static void dumpFunc(Archive *fout, FuncInfo *finfo);
static void dumpCast(Archive *fout, CastInfo *cast);
static void dumpTransform(Archive *fout, TransformInfo *transform);
static void dumpOpr(Archive *fout, OprInfo *oprinfo);
static void dumpAccessMethod(Archive *fout, AccessMethodInfo *oprinfo);
static void dumpOpclass(Archive *fout, OpclassInfo *opcinfo);
static void dumpOpfamily(Archive *fout, OpfamilyInfo *opfinfo);
static void dumpCollation(Archive *fout, CollInfo *convinfo);
@ -1468,6 +1470,26 @@ selectDumpableProcLang(ProcLangInfo *plang, DumpOptions *dopt)
plang->dobj.dump = dopt->include_everything;
}
/*
* selectDumpableAccessMethod: policy-setting subroutine
* Mark an access method as to be dumped or not
*
* Access methods do not belong to any particular namespace. To identify
* built-in access methods, we must resort to checking whether the
* method's OID is in the range reserved for initdb.
*/
static void
selectDumpableAccessMethod(AccessMethodInfo *method, DumpOptions *dopt)
{
if (checkExtensionMembership(&method->dobj, dopt))
return; /* extension membership overrides all else */
if (method->dobj.catId.oid < (Oid) FirstNormalObjectId)
method->dobj.dump = false;
else
method->dobj.dump = dopt->include_everything;
}
/*
* selectDumpableExtension: policy-setting subroutine
* Mark an extension as to be dumped or not
@ -4100,6 +4122,84 @@ getConversions(Archive *fout, int *numConversions)
return convinfo;
}
/*
* getAccessMethods:
* read all user-defined access methods in the system catalogs and return
* them in the AccessMethodInfo* structure
*
* numAccessMethods is set to the number of access methods read in
*/
AccessMethodInfo *
getAccessMethods(Archive *fout, int *numAccessMethods)
{
DumpOptions *dopt = fout->dopt;
PGresult *res;
int ntups;
int i;
PQExpBuffer query;
AccessMethodInfo *aminfo;
int i_tableoid;
int i_oid;
int i_amname;
int i_amhandler;
int i_amtype;
/* Before 9.6, there are no user-defined access methods */
if (fout->remoteVersion < 90600)
{
*numAccessMethods = 0;
return NULL;
}
query = createPQExpBuffer();
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
/*
* Select only user-defined access methods assuming all built-in access
* methods have oid < 10000.
*/
appendPQExpBuffer(query, "SELECT tableoid, oid, amname, amtype, "
"amhandler::pg_catalog.regproc AS amhandler "
"FROM pg_am");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
ntups = PQntuples(res);
*numAccessMethods = ntups;
aminfo = (AccessMethodInfo *) pg_malloc(ntups * sizeof(AccessMethodInfo));
i_tableoid = PQfnumber(res, "tableoid");
i_oid = PQfnumber(res, "oid");
i_amname = PQfnumber(res, "amname");
i_amhandler = PQfnumber(res, "amhandler");
i_amtype = PQfnumber(res, "amtype");
for (i = 0; i < ntups; i++)
{
aminfo[i].dobj.objType = DO_ACCESS_METHOD;
aminfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
aminfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
AssignDumpId(&aminfo[i].dobj);
aminfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_amname));
aminfo[i].dobj.namespace = NULL;
aminfo[i].amhandler = pg_strdup(PQgetvalue(res, i, i_amhandler));
aminfo[i].amtype = *(PQgetvalue(res, i, i_amtype));
/* Decide whether we want to dump it */
selectDumpableAccessMethod(&(aminfo[i]), dopt);
}
PQclear(res);
destroyPQExpBuffer(query);
return aminfo;
}
/*
* getOpclasses:
* read all opclasses in the system catalogs and return them in the
@ -8408,6 +8508,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
case DO_OPERATOR:
dumpOpr(fout, (OprInfo *) dobj);
break;
case DO_ACCESS_METHOD:
dumpAccessMethod(fout, (AccessMethodInfo *) dobj);
break;
case DO_OPCLASS:
dumpOpclass(fout, (OpclassInfo *) dobj);
break;
@ -11446,6 +11549,74 @@ convertTSFunction(Archive *fout, Oid funcOid)
return result;
}
/*
* dumpAccessMethod
* write out a single access method definition
*/
static void
dumpAccessMethod(Archive *fout, AccessMethodInfo *aminfo)
{
DumpOptions *dopt = fout->dopt;
PQExpBuffer q;
PQExpBuffer delq;
PQExpBuffer labelq;
char *qamname;
/* Skip if not to be dumped */
if (!aminfo->dobj.dump || dopt->dataOnly)
return;
q = createPQExpBuffer();
delq = createPQExpBuffer();
labelq = createPQExpBuffer();
qamname = pg_strdup(fmtId(aminfo->dobj.name));
appendPQExpBuffer(q, "CREATE ACCESS METHOD %s ", qamname);
switch (aminfo->amtype)
{
case AMTYPE_INDEX:
appendPQExpBuffer(q, "TYPE INDEX ");
break;
default:
write_msg(NULL, "WARNING: invalid type %c of access method %s\n",
aminfo->amtype, qamname);
destroyPQExpBuffer(q);
destroyPQExpBuffer(delq);
destroyPQExpBuffer(labelq);
return;
}
appendPQExpBuffer(q, "HANDLER %s;\n", aminfo->amhandler);
appendPQExpBuffer(delq, "DROP ACCESS METHOD %s;\n",
qamname);
appendPQExpBuffer(labelq, "ACCESS METHOD %s",
qamname);
ArchiveEntry(fout, aminfo->dobj.catId, aminfo->dobj.dumpId,
aminfo->dobj.name,
NULL,
NULL,
"",
false, "ACCESS METHOD", SECTION_PRE_DATA,
q->data, delq->data, NULL,
NULL, 0,
NULL, NULL);
/* Dump Access Method Comments */
dumpComment(fout, labelq->data,
NULL, "",
aminfo->dobj.catId, 0, aminfo->dobj.dumpId);
free(qamname);
destroyPQExpBuffer(q);
destroyPQExpBuffer(delq);
destroyPQExpBuffer(labelq);
}
/*
* dumpOpclass
@ -16227,6 +16398,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_FUNC:
case DO_AGG:
case DO_OPERATOR:
case DO_ACCESS_METHOD:
case DO_OPCLASS:
case DO_OPFAMILY:
case DO_COLLATION:

View File

@ -48,6 +48,7 @@ typedef enum
DO_FUNC,
DO_AGG,
DO_OPERATOR,
DO_ACCESS_METHOD,
DO_OPCLASS,
DO_OPFAMILY,
DO_COLLATION,
@ -167,6 +168,13 @@ typedef struct _oprInfo
Oid oprcode;
} OprInfo;
typedef struct _accessMethodInfo
{
DumpableObject dobj;
char amtype;
char *amhandler;
} AccessMethodInfo;
typedef struct _opclassInfo
{
DumpableObject dobj;
@ -548,6 +556,7 @@ extern TypeInfo *getTypes(Archive *fout, int *numTypes);
extern FuncInfo *getFuncs(Archive *fout, int *numFuncs);
extern AggInfo *getAggregates(Archive *fout, int *numAggregates);
extern OprInfo *getOperators(Archive *fout, int *numOperators);
extern AccessMethodInfo *getAccessMethods(Archive *fout, int *numAccessMethods);
extern OpclassInfo *getOpclasses(Archive *fout, int *numOpclasses);
extern OpfamilyInfo *getOpfamilies(Archive *fout, int *numOpfamilies);
extern CollInfo *getCollations(Archive *fout, int *numCollations);

View File

@ -28,8 +28,8 @@ static const char *modulename = gettext_noop("sorter");
* by OID. (This is a relatively crude hack to provide semi-reasonable
* behavior for old databases without full dependency info.) Note: collations,
* extensions, text search, foreign-data, materialized view, event trigger,
* policies, transforms, and default ACL objects can't really happen here, so the rather
* bogus priorities for them don't matter.
* policies, transforms, access methods and default ACL objects can't really
* happen here, so the rather bogus priorities for them don't matter.
*
* NOTE: object-type priorities must match the section assignments made in
* pg_dump.c; that is, PRE_DATA objects must sort before DO_PRE_DATA_BOUNDARY,
@ -45,6 +45,7 @@ static const int oldObjectTypePriority[] =
2, /* DO_FUNC */
3, /* DO_AGG */
3, /* DO_OPERATOR */
3, /* DO_ACCESS_METHOD */
4, /* DO_OPCLASS */
4, /* DO_OPFAMILY */
4, /* DO_COLLATION */
@ -95,6 +96,7 @@ static const int newObjectTypePriority[] =
6, /* DO_FUNC */
7, /* DO_AGG */
8, /* DO_OPERATOR */
8, /* DO_ACCESS_METHOD */
9, /* DO_OPCLASS */
9, /* DO_OPFAMILY */
3, /* DO_COLLATION */
@ -1329,6 +1331,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"OPERATOR %s (ID %d OID %u)",
obj->name, obj->dumpId, obj->catId.oid);
return;
case DO_ACCESS_METHOD:
snprintf(buf, bufsize,
"ACCESS METHOD %s (ID %d OID %u)",
obj->name, obj->dumpId, obj->catId.oid);
return;
case DO_OPCLASS:
snprintf(buf, bufsize,
"OPERATOR CLASS %s (ID %d OID %u)",

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201603211
#define CATALOG_VERSION_NO 201603231
#endif

View File

@ -134,6 +134,7 @@ typedef enum ObjectClass
OCLASS_OPERATOR, /* pg_operator */
OCLASS_OPCLASS, /* pg_opclass */
OCLASS_OPFAMILY, /* pg_opfamily */
OCLASS_AM, /* pg_am */
OCLASS_AMOP, /* pg_amop */
OCLASS_AMPROC, /* pg_amproc */
OCLASS_REWRITE, /* pg_rewrite */

View File

@ -35,6 +35,7 @@ CATALOG(pg_am,2601)
{
NameData amname; /* access method name */
regproc amhandler; /* handler function */
char amtype; /* see AMTYPE_xxx constants below */
} FormData_pg_am;
/* ----------------
@ -48,31 +49,38 @@ typedef FormData_pg_am *Form_pg_am;
* compiler constants for pg_am
* ----------------
*/
#define Natts_pg_am 2
#define Natts_pg_am 3
#define Anum_pg_am_amname 1
#define Anum_pg_am_amhandler 2
#define Anum_pg_am_amtype 3
/* ----------------
* compiler constant for amtype
* ----------------
*/
#define AMTYPE_INDEX 'i' /* index access method */
/* ----------------
* initial contents of pg_am
* ----------------
*/
DATA(insert OID = 403 ( btree bthandler ));
DATA(insert OID = 403 ( btree bthandler i ));
DESCR("b-tree index access method");
#define BTREE_AM_OID 403
DATA(insert OID = 405 ( hash hashhandler ));
DATA(insert OID = 405 ( hash hashhandler i ));
DESCR("hash index access method");
#define HASH_AM_OID 405
DATA(insert OID = 783 ( gist gisthandler ));
DATA(insert OID = 783 ( gist gisthandler i ));
DESCR("GiST index access method");
#define GIST_AM_OID 783
DATA(insert OID = 2742 ( gin ginhandler ));
DATA(insert OID = 2742 ( gin ginhandler i ));
DESCR("GIN index access method");
#define GIN_AM_OID 2742
DATA(insert OID = 4000 ( spgist spghandler ));
DATA(insert OID = 4000 ( spgist spghandler i ));
DESCR("SP-GiST index access method");
#define SPGIST_AM_OID 4000
DATA(insert OID = 3580 ( brin brinhandler ));
DATA(insert OID = 3580 ( brin brinhandler i ));
DESCR("block range index (BRIN) access method");
#define BRIN_AM_OID 3580

View File

@ -91,8 +91,6 @@ extern void IsThereOpClassInNamespace(const char *opcname, Oid opcmethod,
Oid opcnamespace);
extern void IsThereOpFamilyInNamespace(const char *opfname, Oid opfmethod,
Oid opfnamespace);
extern Oid get_am_oid(const char *amname, bool missing_ok);
extern char *get_am_name(Oid amOid);
extern Oid get_opclass_oid(Oid amID, List *opclassname, bool missing_ok);
extern Oid get_opfamily_oid(Oid amID, List *opfamilyname, bool missing_ok);
@ -137,6 +135,13 @@ extern Datum transformGenericOptions(Oid catalogId,
List *options,
Oid fdwvalidator);
/* commands/amcmds.c */
extern ObjectAddress CreateAccessMethod(CreateAmStmt *stmt);
extern void RemoveAccessMethodById(Oid amOid);
extern Oid get_index_am_oid(const char *amname, bool missing_ok);
extern Oid get_am_oid(const char *amname, bool missing_ok);
extern char *get_am_name(Oid amOid);
/* support routines in commands/define.c */
extern char *defGetString(DefElem *def);

View File

@ -402,6 +402,7 @@ typedef enum NodeTag
T_CreatePolicyStmt,
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)

View File

@ -1379,6 +1379,7 @@ typedef struct SetOperationStmt
typedef enum ObjectType
{
OBJECT_ACCESS_METHOD,
OBJECT_AGGREGATE,
OBJECT_AMOP,
OBJECT_AMPROC,
@ -2070,6 +2071,18 @@ typedef struct AlterPolicyStmt
Node *with_check; /* the policy's WITH CHECK condition. */
} AlterPolicyStmt;
/*----------------------
* Create ACCESS METHOD Statement
*----------------------
*/
typedef struct CreateAmStmt
{
NodeTag type;
char *amname; /* access method name */
List *handler_name; /* handler function name */
char amtype; /* type of access method */
} CreateAmStmt;
/* ----------------------
* Create TRIGGER Statement
* ----------------------

View File

@ -239,6 +239,7 @@ PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD)
PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
PG_KEYWORD("method", METHOD, UNRESERVED_KEYWORD)
PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
PG_KEYWORD("mode", MODE, UNRESERVED_KEYWORD)

View File

@ -95,6 +95,48 @@ typedef enum
Pattern_Prefix_None, Pattern_Prefix_Partial, Pattern_Prefix_Exact
} Pattern_Prefix_Status;
/*
* deconstruct_indexquals is a simple function to examine the indexquals
* attached to a proposed IndexPath. It returns a list of IndexQualInfo
* structs, one per qual expression.
*/
typedef struct
{
RestrictInfo *rinfo; /* the indexqual itself */
int indexcol; /* zero-based index column number */
bool varonleft; /* true if index column is on left of qual */
Oid clause_op; /* qual's operator OID, if relevant */
Node *other_operand; /* non-index operand of qual's operator */
} IndexQualInfo;
/*
* genericcostestimate is a general-purpose estimator that can be used for
* most index types. In some cases we use genericcostestimate as the base
* code and then incorporate additional index-type-specific knowledge in
* the type-specific calling function. To avoid code duplication, we make
* genericcostestimate return a number of intermediate values as well as
* its preliminary estimates of the output cost values. The GenericCosts
* struct includes all these values.
*
* Callers should initialize all fields of GenericCosts to zero. In addition,
* they can set numIndexTuples to some positive value if they have a better
* than default way of estimating the number of leaf index tuples visited.
*/
typedef struct
{
/* These are the values the cost estimator must return to the planner */
Cost indexStartupCost; /* index-related startup cost */
Cost indexTotalCost; /* total index-related scan cost */
Selectivity indexSelectivity; /* selectivity of index */
double indexCorrelation; /* order correlation of index */
/* Intermediate values we obtain along the way */
double numIndexPages; /* number of leaf pages visited */
double numIndexTuples; /* number of leaf tuples visited */
double spc_random_page_cost; /* relevant random_page_cost value */
double num_sa_scans; /* # indexscans from ScalarArrayOps */
} GenericCosts;
/* Hooks for plugins to get control when we ask for stats */
typedef bool (*get_relation_stats_hook_type) (PlannerInfo *root,
RangeTblEntry *rte,
@ -191,6 +233,12 @@ extern double estimate_num_groups(PlannerInfo *root, List *groupExprs,
extern Selectivity estimate_hash_bucketsize(PlannerInfo *root, Node *hashkey,
double nbuckets);
extern List *deconstruct_indexquals(IndexPath *path);
extern void genericcostestimate(PlannerInfo *root, IndexPath *path,
double loop_count,
List *qinfos,
GenericCosts *costs);
/* Functions in array_selfuncs.c */
extern Selectivity scalararraysel_containment(PlannerInfo *root,

View File

@ -0,0 +1,108 @@
--
-- Create access method tests
--
-- Make gist2 over gisthandler. In fact, it would be a synonym to gist.
CREATE ACCESS METHOD gist2 TYPE INDEX HANDLER gisthandler;
-- Drop old index on fast_emp4000
DROP INDEX grect2ind;
-- Try to create gist2 index on fast_emp4000: fail because opclass doesn't exist
CREATE INDEX grect2ind ON fast_emp4000 USING gist2 (home_base);
ERROR: data type box has no default operator class for access method "gist2"
HINT: You must specify an operator class for the index or define a default operator class for the data type.
-- Make operator class for boxes using gist2
CREATE OPERATOR CLASS box_ops DEFAULT
FOR TYPE box USING gist2 AS
OPERATOR 1 <<,
OPERATOR 2 &<,
OPERATOR 3 &&,
OPERATOR 4 &>,
OPERATOR 5 >>,
OPERATOR 6 ~=,
OPERATOR 7 @>,
OPERATOR 8 <@,
OPERATOR 9 &<|,
OPERATOR 10 <<|,
OPERATOR 11 |>>,
OPERATOR 12 |&>,
OPERATOR 13 ~,
OPERATOR 14 @,
FUNCTION 1 gist_box_consistent(internal, box, smallint, oid, internal),
FUNCTION 2 gist_box_union(internal, internal),
FUNCTION 3 gist_box_compress(internal),
FUNCTION 4 gist_box_decompress(internal),
FUNCTION 5 gist_box_penalty(internal, internal, internal),
FUNCTION 6 gist_box_picksplit(internal, internal),
FUNCTION 7 gist_box_same(box, box, internal),
FUNCTION 9 gist_box_fetch(internal);
-- Create gist2 index on fast_emp4000
CREATE INDEX grect2ind ON fast_emp4000 USING gist2 (home_base);
-- Now check the results from plain indexscan
SET enable_seqscan = OFF;
SET enable_indexscan = ON;
SET enable_bitmapscan = OFF;
EXPLAIN (COSTS OFF)
SELECT * FROM fast_emp4000
WHERE home_base @ '(200,200),(2000,1000)'::box
ORDER BY (home_base[0])[0];
QUERY PLAN
----------------------------------------------------------------
Sort
Sort Key: ((home_base[0])[0])
-> Index Only Scan using grect2ind on fast_emp4000
Index Cond: (home_base @ '(2000,1000),(200,200)'::box)
(4 rows)
SELECT * FROM fast_emp4000
WHERE home_base @ '(200,200),(2000,1000)'::box
ORDER BY (home_base[0])[0];
home_base
-----------------------
(337,455),(240,359)
(1444,403),(1346,344)
(2 rows)
EXPLAIN (COSTS OFF)
SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box;
QUERY PLAN
-------------------------------------------------------------
Aggregate
-> Index Only Scan using grect2ind on fast_emp4000
Index Cond: (home_base && '(1000,1000),(0,0)'::box)
(3 rows)
SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box;
count
-------
2
(1 row)
EXPLAIN (COSTS OFF)
SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL;
QUERY PLAN
-------------------------------------------------------
Aggregate
-> Index Only Scan using grect2ind on fast_emp4000
Index Cond: (home_base IS NULL)
(3 rows)
SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL;
count
-------
278
(1 row)
-- Try to drop access method: fail because of depending objects
DROP ACCESS METHOD gist2;
ERROR: cannot drop access method gist2 because other objects depend on it
DETAIL: operator class box_ops for access method gist2 depends on access method gist2
index grect2ind depends on operator class box_ops for access method gist2
HINT: Use DROP ... CASCADE to drop the dependent objects too.
-- Drop access method cascade
DROP ACCESS METHOD gist2 CASCADE;
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to operator class box_ops for access method gist2
drop cascades to index grect2ind
-- Reset optimizer options
RESET enable_seqscan;
RESET enable_indexscan;
RESET enable_bitmapscan;

View File

@ -325,6 +325,10 @@ SELECT pg_get_object_address('event trigger', '{one}', '{}');
ERROR: event trigger "one" does not exist
SELECT pg_get_object_address('event trigger', '{one,two}', '{}');
ERROR: event trigger name cannot be qualified
SELECT pg_get_object_address('access method', '{one}', '{}');
ERROR: access method "one" does not exist
SELECT pg_get_object_address('access method', '{one,two}', '{}');
ERROR: access method name cannot be qualified
-- test successful cases
WITH objects (type, name, args) AS (VALUES
('table', '{addr_nsp, gentable}'::text[], '{}'::text[]),
@ -373,7 +377,8 @@ WITH objects (type, name, args) AS (VALUES
-- extension
-- event trigger
('policy', '{addr_nsp, gentable, genpol}', '{}'),
('transform', '{int}', '{sql}')
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}')
)
SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.subobjid)).*,
-- test roundtrip through pg_identify_object_as_address
@ -405,6 +410,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.subobjid)).*,
server | | addr_fserv | addr_fserv | t
user mapping | | | regtest_addr_user on server integer | t
foreign-data wrapper | | addr_fdw | addr_fdw | t
access method | | btree | btree | t
operator of access method | | | operator 1 (integer, integer) of pg_catalog.integer_ops USING btree | t
function of access method | | | function 2 (integer, integer) of pg_catalog.integer_ops USING btree | t
default value | | | for addr_nsp.gentable.b | t
@ -426,7 +432,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.subobjid)).*,
text search parser | addr_nsp | addr_ts_prs | addr_nsp.addr_ts_prs | t
text search configuration | addr_nsp | addr_ts_conf | addr_nsp.addr_ts_conf | t
text search template | addr_nsp | addr_ts_temp | addr_nsp.addr_ts_temp | t
(41 rows)
(42 rows)
---
--- Cleanup resources

View File

@ -44,7 +44,7 @@ e_star|f
emp|f
equipment_r|f
f_star|f
fast_emp4000|t
fast_emp4000|f
float4_tbl|f
float8_tbl|f
func_index_heap|t

View File

@ -60,7 +60,7 @@ test: create_index create_view
# ----------
# Another group of parallel tests
# ----------
test: create_aggregate create_function_3 create_cast constraints triggers inherit create_table_like typed_table vacuum drop_if_exists updatable_views rolenames roleattributes
test: create_aggregate create_function_3 create_cast constraints triggers inherit create_table_like typed_table vacuum drop_if_exists updatable_views rolenames roleattributes create_am
# ----------
# sanity_check does a vacuum, affecting the sort order of SELECT *

View File

@ -75,6 +75,7 @@ test: drop_if_exists
test: updatable_views
test: rolenames
test: roleattributes
test: create_am
test: sanity_check
test: errors
test: select

View File

@ -0,0 +1,73 @@
--
-- Create access method tests
--
-- Make gist2 over gisthandler. In fact, it would be a synonym to gist.
CREATE ACCESS METHOD gist2 TYPE INDEX HANDLER gisthandler;
-- Drop old index on fast_emp4000
DROP INDEX grect2ind;
-- Try to create gist2 index on fast_emp4000: fail because opclass doesn't exist
CREATE INDEX grect2ind ON fast_emp4000 USING gist2 (home_base);
-- Make operator class for boxes using gist2
CREATE OPERATOR CLASS box_ops DEFAULT
FOR TYPE box USING gist2 AS
OPERATOR 1 <<,
OPERATOR 2 &<,
OPERATOR 3 &&,
OPERATOR 4 &>,
OPERATOR 5 >>,
OPERATOR 6 ~=,
OPERATOR 7 @>,
OPERATOR 8 <@,
OPERATOR 9 &<|,
OPERATOR 10 <<|,
OPERATOR 11 |>>,
OPERATOR 12 |&>,
OPERATOR 13 ~,
OPERATOR 14 @,
FUNCTION 1 gist_box_consistent(internal, box, smallint, oid, internal),
FUNCTION 2 gist_box_union(internal, internal),
FUNCTION 3 gist_box_compress(internal),
FUNCTION 4 gist_box_decompress(internal),
FUNCTION 5 gist_box_penalty(internal, internal, internal),
FUNCTION 6 gist_box_picksplit(internal, internal),
FUNCTION 7 gist_box_same(box, box, internal),
FUNCTION 9 gist_box_fetch(internal);
-- Create gist2 index on fast_emp4000
CREATE INDEX grect2ind ON fast_emp4000 USING gist2 (home_base);
-- Now check the results from plain indexscan
SET enable_seqscan = OFF;
SET enable_indexscan = ON;
SET enable_bitmapscan = OFF;
EXPLAIN (COSTS OFF)
SELECT * FROM fast_emp4000
WHERE home_base @ '(200,200),(2000,1000)'::box
ORDER BY (home_base[0])[0];
SELECT * FROM fast_emp4000
WHERE home_base @ '(200,200),(2000,1000)'::box
ORDER BY (home_base[0])[0];
EXPLAIN (COSTS OFF)
SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box;
SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box;
EXPLAIN (COSTS OFF)
SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL;
SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL;
-- Try to drop access method: fail because of depending objects
DROP ACCESS METHOD gist2;
-- Drop access method cascade
DROP ACCESS METHOD gist2 CASCADE;
-- Reset optimizer options
RESET enable_seqscan;
RESET enable_indexscan;
RESET enable_bitmapscan;

View File

@ -117,6 +117,8 @@ SELECT pg_get_object_address('extension', '{one}', '{}');
SELECT pg_get_object_address('extension', '{one,two}', '{}');
SELECT pg_get_object_address('event trigger', '{one}', '{}');
SELECT pg_get_object_address('event trigger', '{one,two}', '{}');
SELECT pg_get_object_address('access method', '{one}', '{}');
SELECT pg_get_object_address('access method', '{one,two}', '{}');
-- test successful cases
WITH objects (type, name, args) AS (VALUES
@ -166,7 +168,8 @@ WITH objects (type, name, args) AS (VALUES
-- extension
-- event trigger
('policy', '{addr_nsp, gentable, genpol}', '{}'),
('transform', '{int}', '{sql}')
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}')
)
SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.subobjid)).*,
-- test roundtrip through pg_identify_object_as_address