postgresql-portal/

Изучая исходный код PostgreSQL, можно повстречать сущность под названием Portal. Возникает закономерный вопрос — что это такое, а также где и для чего используется? Попробуем разобраться.

Чтобы вживую увидеть Portal, подключимся gdb к бэкенду PostgreSQL и поставим следующую точку останова:

(gdb) b DefineRelation
(gdb) c

Если теперь выполнить запрос:

CREATE TABLE «data» ( «id» INT ) ;

… то будет получен такой стэктрейс:

(gdb) bt
#0  DefineRelation
#1  0x000055559a5ebe88 in ProcessUtilitySlow
#2  0x000055559a5ebc80 in standard_ProcessUtility
#3  0x000055559a5eafa8 in ProcessUtility
#4  0x000055559a5e87b8 in PortalRunUtility
#5  0x000055559a5e8a44 in PortalRunMulti
#6  0x000055559a5e7eb8 in PortalRun (portal=0x5555c6a287b0, … )
#7  0x000055559a5df9e4 in exec_simple_query
#8  0x000055559a5e4e04 in PostgresMain
#9  0x000055559a5db8b8 in BackendMain
#10 0x000055559a4ed70c in postmaster_child_launch
#11 0x000055559a4f4668 in BackendStartup
#12 0x000055559a4f0d30 in ServerLoop
#13 0x000055559a4f0598 in PostmasterMain
#14 0x000055559a3dc638 in main

Продолжая экспериментировать с разными запросами, мы убеждаемся, что их исполнение всегда происходит через PortalRun(), где portal фигурирует в качестве аргумента. За дальнейшими разъяснениями обратимся к исходному коду в portalmem.c и portal.h .

Из кода мы узнаем, что же собой представляет портал. Portal — это структура, отражающая состояние исполнения запроса. Один Portal соответствует одному SQL-запросу и, соответственно, имеет один возвращаемый результат.

Портал реализован, как структура PortalData . В ней немало полей. Все их мы разбирать не буем, ограничившись лишь парой наиболее интересных:

MemoryContext portalContext ;

Портал имеет выделенный ему MemoryContext . C MemoryContext’ами к этому моменту мы уже знакомы.

ResourceOwner resowner ;

Также каждому порталу присваивается ResourceOwner. Это отдельная важная структура, однако ее обсуждение выходит за рамки поста. Если в двух словах, то ResourceOwner отвечает за своевременное освобождение ресурсов, таких как открытые файлы, pin’ы на разделяемые буферы , и так далее.

void ( * cleanup ) ( Portal portal ) ;

Хук, вызываемый при освобождении портала. Как правило, это PortalCleanup() , объявленный в portalcmds.c .

const char * sourceText ;

Текст SQL-запроса. В PostgreSQL ≥ 8.4 здесь не может быть NULL.

ParamListInfo portalParams ;

Параметры запроса.

Чтобы посмотреть, как эта структура заполняется, в psql выполним:

PREPARE foo ( INT ) AS SELECT $1;

Затем создадим точку останова на PortalRun() и выполним запрос:

EXECUTE foo ( 123 ) ;

Точка останова срабатывает дважды:

#0  PortalRun                                   <- второй раз
#1  0x000055559a2cc158 in ExecuteQuery
#2  0x000055559a5eb5e0 in standard_ProcessUtility
#3  0x000055559a5eafa8 in ProcessUtility
#4  0x000055559a5e87b8 in PortalRunUtility
#5  0x000055559a5e84f8 in FillPortalStore
#6  0x000055559a5e7e2c in PortalRun             <- первый раз
#7  0x000055559a5df9e4 in exec_simple_query
#8  0x000055559a5e4e04 in PostgresMain
#9  0x000055559a5db8b8 in BackendMain
#10 0x000055559a4ed70c in postmaster_child_launch
#11 0x000055559a4f4668 in BackendStartup
#12 0x000055559a4f0d30 in ServerLoop
#13 0x000055559a4f0598 in PostmasterMain
#14 0x000055559a3dc638 in main

Значение полей при первом вызове функции:

(gdb) p portal->sourceText
$1 = 0x5555c69b31c0 «EXECUTE foo(123);»

(gdb) p portal->portalParams
$2 = (ParamListInfo) 0x0

… и при втором:

(gdb) p portal->sourceText
$3 = 0x5555c6ac73f0 «PREPARE foo(int) AS SELECT $1;»

(gdb) p portal->portalParams->numParams
$4 = 1

(gdb) p portal->portalParams->params[0]
$5 = {value = 123, isnull = false, pflags = 1, ptype = 23}

Видим, как через portalParams был передан аргумент для prepared statement .

Над порталами определен ряд функций, такие как CreatePortal(), PortalDrop(), PortalDefineQuery(), GetPortalByName() и другие. Полный список есть в portal.h , а датели реализации можно найти в portalmem.c . Заинтересованные читатели могут ознакомиться с этими функциями самостоятельно. Они простые.

Это базовые операции над порталами, а вся логика находится в файле pquery.c . Основных функций две — это PortalStart() :

/* Подготовить портал к исполнению */
void
PortalStart ( Portal portal , ParamListInfo params ,
int eflags , Snapshot snapshot )

… и PortalRun() :

/* Исполнить запрос из портала */
bool
PortalRun ( Portal portal , long count , bool isTopLevel , bool run_once ,
DestReceiver * dest , DestReceiver * altdest ,
QueryCompletion * qc )

В pquery.c есть комментарии относительно всех аргументов, возвращаемых значений, и т.д. Пример практического использования функций CreatePortal(), PortalDefineQuery(), PortalStart(), PortalRun() и PortalDrop() можно найти в функции exec_simple_query() , уже знакомой нам по представленным выше стектрейсам.

Конечно же, полная картина сложнее. Однако углубляться в изучение порталов еще сильнее как будто бы нет особого смысла. Даже разработчики PostgreSQL исключительно редко трогают порталы. В этом легко убедиться при помощи команды git log . Обычно достаточно в общих чертах понимать, что они из себя представляют. Если же когда-нибудь вам придется с головой погрузиться в детали реализации, то теперь у вас есть отправная точка. Для получения самой полной и актуальной информации рекомендуется читать код и ставить эксперименты под отладчиком.

Дополнение: В продолжение темы порталов вас может заинтересовать статья Внутренности PostgreSQL: сетевой протокол .

EnglishRussianUkrainian