diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml index 5f7befb858..b36889b856 100644 --- a/doc/src/sgml/indexam.sgml +++ b/doc/src/sgml/indexam.sgml @@ -57,6 +57,12 @@ to insert an appropriate row for themselves. + + Index access access methods can be defined and dropped using + and + SQL commands respectively. + + An index access method handler function must be declared to accept a single argument of type internal and to return the diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index bf95453b6c..77667bdebd 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -52,6 +52,7 @@ Complete list of usable sgml source files in this directory. + @@ -94,6 +95,7 @@ Complete list of usable sgml source files in this directory. + diff --git a/doc/src/sgml/ref/create_access_method.sgml b/doc/src/sgml/ref/create_access_method.sgml new file mode 100644 index 0000000000..3c091f8021 --- /dev/null +++ b/doc/src/sgml/ref/create_access_method.sgml @@ -0,0 +1,120 @@ + + + + + CREATE ACCESS METHOD + + + + CREATE ACCESS METHOD + 7 + SQL - Language Statements + + + + CREATE ACCESS METHOD + define a new access method + + + + +CREATE ACCESS METHOD name + TYPE access_method_type + HANDLER handler_function + + + + + Description + + + CREATE ACCESS METHOD creates a new access method. + + + + The access method name must be unique within the database. + + + + Only superusers can define new access methods. + + + + + Parameters + + + + name + + + The name of the access method to be created. + + + + + + access_method_type + + + This clause specifies type of access method to define. + Only INDEX is supported at present. + + + + + + HANDLER handler_function + + handler_function 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 internal, and its return type depends on the + type of access method; for INDEX access methods, it + must be index_am_handler. + + + + See for index access methods API. + + + + + + + + Examples + + + Create an access method heptree with + handler function heptree_handler: + +CREATE ACCESS METHOD heptree TYPE INDEX HANDLER heptree_handler; + + + + + + Compatibility + + + CREATE ACCESS METHOD is a + PostgreSQL extension. + + + + + See Also + + + + + + + + + diff --git a/doc/src/sgml/ref/drop_access_method.sgml b/doc/src/sgml/ref/drop_access_method.sgml new file mode 100644 index 0000000000..97ed77ebda --- /dev/null +++ b/doc/src/sgml/ref/drop_access_method.sgml @@ -0,0 +1,112 @@ + + + + + DROP ACCESS METHOD + + + + DROP ACCESS METHOD + 7 + SQL - Language Statements + + + + DROP ACCESS METHOD + remove an access method + + + + +DROP ACCESS METHOD [ IF EXISTS ] name [ CASCADE | RESTRICT ] + + + + + Description + + + DROP ACCESS METHOD removes an existing access method. + Only superusers can drop access methods. + + + + + + + + Parameters + + + + IF EXISTS + + + Do not throw an error if the access method does not exist. + A notice is issued in this case. + + + + + + name + + + The name of an existing access method. + + + + + + CASCADE + + + Automatically drop objects that depend on the access method + (such as operator classes, operator families, indexes). + + + + + + RESTRICT + + + Refuse to drop the access method if any objects depend on it. + This is the default. + + + + + + + + Examples + + + Drop the access method heptree: + +DROP ACCESS METHOD heptree; + + + + + Compatibility + + + DROP ACCESS METHOD is a + PostgreSQL extension. + + + + + See Also + + + + + + + diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index 03020dfec4..8acdff1393 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -80,6 +80,7 @@ &commit; &commitPrepared; ©Table; + &createAccessMethod; &createAggregate; &createCast; &createCollation; @@ -122,6 +123,7 @@ &delete; &discard; &do; + &dropAccessMethod; &dropAggregate; &dropCast; &dropCollation; diff --git a/src/backend/access/index/amapi.c b/src/backend/access/index/amapi.c index bda166a9ef..d347ebcba4 100644 --- a/src/backend/access/index/amapi.c +++ b/src/backend/access/index/amapi.c @@ -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 */ diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index c48e37bf9a..17f9de1ff9 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -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; diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index d2aaa6ded9..cb3ba853f4 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -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, diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index b1ac704886..6b3742c0a0 100644 --- a/src/backend/commands/Makefile +++ b/src/backend/commands/Makefile @@ -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 \ diff --git a/src/backend/commands/amcmds.c b/src/backend/commands/amcmds.c new file mode 100644 index 0000000000..7a93754391 --- /dev/null +++ b/src/backend/commands/amcmds.c @@ -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; +} diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 9e32f8d09b..3f52ad836b 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -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; } diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c index 8a661968cd..ac559fc9b4 100644 --- a/src/backend/commands/opclasscmds.c +++ b/src/backend/commands/opclasscmds.c @@ -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; -} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 6b5d1d6efc..6378db8bbe 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -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; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 87eb859e05..854c062d32 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -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; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index a74fb772e1..12733528eb 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -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 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 diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index dc431c7de0..65284941ed 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -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), diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 045f7f06ee..4d0aac979f 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -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: { diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index d396ef142f..b2c57e87a5 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -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, diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index f798b15e3c..1acd91ab44 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -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); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 64c2673f9a..b3ef201a3a 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -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: diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 9a1d8f863c..66e693183a 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -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); diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index 78ff59c342..36de6b6257 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -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)", diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 3568cb27e8..b690dc6b34 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201603211 +#define CATALOG_VERSION_NO 201603231 #endif diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 049bf9f5a2..d41abc4e48 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -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 */ diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h index f801c3ee57..1116923799 100644 --- a/src/include/catalog/pg_am.h +++ b/src/include/catalog/pg_am.h @@ -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 diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 54f67e9eea..b064eb4836 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -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); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 42c958258b..734df771eb 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -402,6 +402,7 @@ typedef enum NodeTag T_CreatePolicyStmt, T_AlterPolicyStmt, T_CreateTransformStmt, + T_CreateAmStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 2fd06295e5..8b958b422c 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -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 * ---------------------- diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 6e1e82027c..7de3404aa2 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -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) diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h index 06fbca719b..8e0d317468 100644 --- a/src/include/utils/selfuncs.h +++ b/src/include/utils/selfuncs.h @@ -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, diff --git a/src/test/regress/expected/create_am.out b/src/test/regress/expected/create_am.out new file mode 100644 index 0000000000..47d6024610 --- /dev/null +++ b/src/test/regress/expected/create_am.out @@ -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; diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out index 75751bebef..5310c956fe 100644 --- a/src/test/regress/expected/object_address.out +++ b/src/test/regress/expected/object_address.out @@ -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 diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index eb0bc88ef1..2c5be4bae4 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -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 diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index bec03165a7..8be4b831a1 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -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 * diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 7e9b319b0f..1de3da8bac 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -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 diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql new file mode 100644 index 0000000000..e2051c5fcd --- /dev/null +++ b/src/test/regress/sql/create_am.sql @@ -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; diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql index 68e7cb0eb3..b5662349bc 100644 --- a/src/test/regress/sql/object_address.sql +++ b/src/test/regress/sql/object_address.sql @@ -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