Глава 60. Определение интерфейса для табличных методов доступа
В этой главе описывается интерфейс между ядром системы PostgreSQL и табличными методами доступа, которые управляют хранением таблиц. Ядро системы не знает об этих метода доступа ничего, кроме того, что описано здесь; благодаря этому можно реализовывать абсолютно новые типы методов в рамках расширений.
Каждый табличный метод доступа описывается строкой в системном каталоге pg_am
. В записи pg_am
указывается имя и функция-обработчик для этого табличного метода. Эти записи могут создаваться и удаляться командами SQL CREATE ACCESS METHOD и DROP ACCESS METHOD.
Функция-обработчик табличного метода доступа должна объявляться как принимающая один аргумент типа internal
и возвращающая псевдотип table_am_handler
. Аргумент в данном случае фиктивный и нужен только для того, чтобы эту функцию нельзя было вызывать непосредственно из команд SQL. Возвращать эта функция должна указатель на структуру типа TableAmRoutine
, содержащую всё, что нужно знать коду ядра, чтобы использовать этот метод доступа. Возвращаемое значение должно существовать всё время жизни сервера, что обычно достигается объявлением глобальной переменной static const
. Структура TableAmRoutine
, также называемая структурой API метода доступа, определяет поведение метода доступа через обработчики. Эти обработчики задаются как обычные указатели на функции уровня C, поэтому они не видны и не доступны на уровне SQL. Все обработчики и их свойства задаются в структуре TableAmRoutine
, в определении которой можно найти комментарии с требованиями к ним. Для большинства обработчиков созданы функции-обёртки, документированные с точки зрения пользователя (а не разработчика) табличного метода доступа. Более подробно это описывается в файле src/include/access/tableam.h
.
Чтобы реализовать метод доступа (МД), разработчик обычно должен создать для него специальный слот таблицы кортежей (см. src/include/executor/tuptable.h
), позволяющий коду снаружи метода доступа иметь ссылки на кортежи данного МД и обращаться к столбцам кортежа.
В настоящее время способ хранения данных, определяемый МД, может быть практически любым. Например, МД может по своему усмотрению использовать кеш общих буферов. Если этот кеш используется, скорее всего имеет смысл применять и стандартную компоновку страницы, описанную в Разделе 69.6.
Одним довольно серьёзным ограничением API табличных методов доступа является то, что в настоящее время МД может поддерживать модификации данных и/или индексы, только если для каждого кортежа имеется идентификатор (TID), состоящий из номера блока и номера элемента (см. также Раздел 69.6). Компоненты TIDs в принципе могут иметь значение, отличное от принятого для метода heap
, но если желательно поддерживать сканирование по битовой карте (вообще это не обязательно), номер блока должен обеспечивать локальность данных.
Для восстановления при сбое МД может использовать WAL сервера или собственную реализацию журнала. В случае использования WAL в МД можно задействовать Унифицированные записи WAL или реализовать свой тип записей WAL. Использовать унифицированные записи проще, но они занимают больше места в WAL. Для реализации нового типа записей WAL в настоящее время необходимо вносить изменения в код ядра (в частности, в src/include/access/rmgrlist.h
).
Чтобы реализовать поддержку транзакций способом, позволяющим обращаться к различным табличным методам в одной транзакции, скорее всего потребуется интегрировать её в механизм src/backend/access/transam/xlog.c
.
Разработчик нового табличного метода доступа
может почерпнуть другую полезную информацию из реализации существующего метода heap
, находящейся в src/backend/access/heap/heapam_handler.c
.
Chapter 60. Table Access Method Interface Definition
This chapter explains the interface between the core PostgreSQL system and table access methods, which manage the storage for tables. The core system knows little about these access methods beyond what is specified here, so it is possible to develop entirely new access method types by writing add-on code.
Each table access method is described by a row in the pg_am
system catalog. The pg_am
entry specifies a name and a handler function for the table access method. These entries can be created and deleted using the CREATE ACCESS METHOD and DROP ACCESS METHOD SQL commands.
A table access method handler function must be declared to accept a single argument of type internal
and to return the pseudo-type table_am_handler
. The argument is a dummy value that simply serves to prevent handler functions from being called directly from SQL commands. The result of the function must be a pointer to a struct of type TableAmRoutine
, which contains everything that the core code needs to know to make use of the table access method. The return value needs to be of server lifetime, which is typically achieved by defining it as a static const
variable in global scope. The TableAmRoutine
struct, also called the access method's API struct, defines the behavior of the access method using callbacks. These callbacks are pointers to plain C functions and are not visible or callable at the SQL level. All the callbacks and their behavior is defined in the TableAmRoutine
structure (with comments inside the struct defining the requirements for callbacks). Most callbacks have wrapper functions, which are documented from the point of view of a user (rather than an implementor) of the table access method. For details, please refer to the src/include/access/tableam.h
file.
To implement an access method, an implementor will typically need to implement an AM-specific type of tuple table slot (see src/include/executor/tuptable.h
), which allows code outside the access method to hold references to tuples of the AM, and to access the columns of the tuple.
Currently, the way an AM actually stores data is fairly unconstrained. For example, it's possible, but not required, to use postgres' shared buffer cache. In case it is used, it likely makes sense to use PostgreSQL's standard page layout as described in Section 69.6.
One fairly large constraint of the table access method API is that, currently, if the AM wants to support modifications and/or indexes, it is necessary for each tuple to have a tuple identifier (TID) consisting of a block number and an item number (see also Section 69.6). It is not strictly necessary that the sub-parts of TIDs have the same meaning they e.g., have for heap
, but if bitmap scan support is desired (it is optional), the block number needs to provide locality.
For crash safety, an AM can use postgres' WAL, or a custom implementation. If WAL is chosen, either Generic WAL Records can be used, or a new type of WAL records can be implemented. Generic WAL Records are easy, but imply higher WAL volume. Implementation of a new type of WAL record currently requires modifications to core code (specifically, src/include/access/rmgrlist.h
).
To implement transactional support in a manner that allows different table access methods be accessed within a single transaction, it likely is necessary to closely integrate with the machinery in src/backend/access/transam/xlog.c
.
Any developer of a new table access method
can refer to the existing heap
implementation present in src/backend/access/heap/heapam_handler.c
for details of its implementation.