74.2. Исходные данные системных каталогов

Для каждого каталога, с которым связаны вручную создаваемые исходные данные, (не все каталоги такие) имеется соответствующий файл .dat, содержащий эти данные в редактируемом формате.

74.2.1. Формат файла данных

Каждый файл .dat содержит описания структур данных Perl, в результате вычисления которых (функцией eval) в памяти формируется структура данных, состоящая из массива хеш-ссылок, соответствующих каждой строке каталога. Немного модифицированная выдержка из pg_database.dat иллюстрирует основные моменты:

[

# A comment could appear here.
{ oid => '1', oid_symbol => 'Template1DbOid',
  descr => 'database\'s default template',
  datname => 'template1', encoding => 'ENCODING',
  datlocprovider => 'LOCALE_PROVIDER', datistemplate => 't',
  datallowconn => 't', datconnlimit => '-1', datfrozenxid => '0',
  datminmxid => '1', dattablespace => 'pg_default', datcollate => 'LC_COLLATE',
  datctype => 'LC_CTYPE', daticulocale => 'ICU_LOCALE', datacl => '_null_' },

]

Замечания:

  • Общий формат файла: открывающая квадратная скобка, один или более наборов фигурных скобок, каждый из которых представляет строку каталога, и закрывающая квадратная скобка. После каждой закрывающей фигурной скобки должна идти запятая.

  • В каждой строке каталога записываются разделённые запятыми пары ключ => значение. В качестве ключа принимаются имена столбцов каталога, а также ключи метаданных oid, array_type_oid, oid_symbol и descr. (Использование oid и oid_symbol описывается в Подразделе 74.2.2, а array_type_oid — в Подразделе 74.2.4. В descr задаётся строка с описанием объекта, которое будет вставлено в pg_description или pg_shdescription.) Ключи метаданных могут опускаться, но ключ для каждого столбца каталога должен присутствовать, если только в файле .h данного каталога для столбца не задано значение по умолчанию. (Для иллюстрации в показанном выше примере опущено поле datdba, так как в pg_database.h для него задаётся подходящее значение по умолчанию.)

  • Все значения должны заключаться в апострофы. Апострофы внутри значений экранируются обратной косой чертой. Обратные косые черты в данных могут, но не обязательно должны дублироваться; это соответствует правилам Perl по оформлению простых строковых констант. Заметьте, что обратные косые черты, фигурирующие в данных, будут обрабатываться сканером исходных данных как символы экранирования, согласно тем же правилам записи строковых констант (см. Подраздел 4.1.2.2); например, \t преобразуется в символ табуляции. Если вы хотите получить именно обратную косую черту в окончательном значении, вам надо будет написать четыре этих символа: Perl отбрасывает два и оставляет \\ сканеру исходных данных.

  • Значения NULL представляются как _null_. (Заметьте, что создать значение с именно такой строкой невозможно.)

  • Комментарии предваряются знаком # и должны размещаться в отдельных строках.

  • Значения полей, выражающие OID других записей каталога, должны представляться символьными именами, а не числовыми кодами OID. (В примере выше такое символьное значение задаётся для поля dattablespace.) Об этом рассказывается в Подразделе 74.2.3.

  • Так как хеши являются неупорядоченной структурой данных, порядок полей и расположение строк не имеют семантической значимости. Однако для поддержания согласованного представления мы установили несколько правил, которые применяет скрипт форматирования reformat_dat_file.pl:

    • В каждой паре фигурных скобок сначала по порядку идут поля метаданных oid, oid_symbol, array_type_oid и descr (в случае наличия), а за ними собственные поля каталога в определённом для них порядке.

    • Переводы строк при необходимости вставляются между полями для ограничения длины строки 80 символами, если это возможно. Перевод строки также вставляется между полями метаданных и обычными полями.

    • Если в файле .h каталога задаётся значение по умолчанию для столбца и то же значение указано в записи данных, reformat_dat_file.pl уберёт его из файла данных. Таким образом обеспечивается компактное представление данных.

    • Скрипт reformat_dat_file.pl сохраняет пустые строки и строки комментариев в неизменном виде.

    Скрипт reformat_dat_file.pl рекомендуется запускать перед сохранением изменений в данных каталога. Им удобно пользоваться, просто выполняя make reformat-dat-files в src/include/catalog/.

  • Если вы хотите добавить новый метод уменьшения представления данных, вы должны реализовать его в reformat_dat_file.pl и также научить Catalog::ParseData() разворачивать данные в полное представление.

74.2.2. Назначение OID

Строке каталога, фигурирующей в исходных данных, можно вручную присвоить OID, добавив поле метаданных oid => nnnn. Более того, когда строке присваивается OID, для этого OID можно создать макрос C, добавив поле метаданных oid_symbol => имя.

Предварительно загружаемым строкам каталога должны заранее назначаться OID, если на них по OID ссылаются другие предварительно загружаемые строки. Назначать OID также требуется, если на OID нужно будет ссылаться из кода на C. В отсутствие этих условий поле метаданных oid можно опустить, и тогда загрузочный код назначит OID автоматически. На практике мы обычно явно назначаем OID для всех строк в определённом каталоге (даже если фактически присутствуют ссылки только на часть из них) либо не назначаем их вовсе.

Указание фактического числового значения любого OID в коде на C считается крайне нежелательным; вместо этого всегда следует использовать макрос. Прямые обращения к OID в pg_proc требуются достаточно часто, поэтому был создан специальный механизм, создающий необходимые макросы автоматически; см. src/backend/utils/Gen_fmgrtab.pl. С аналогичной целью предусмотрен (но по историческим причинам реализован по-другому) метод создания макросов для OID в pg_type. Как следствие, записи oid_symbol в этих двух каталогах добавлять не нужно. Подобным образом в pg_class автоматически включаются макросы для OID системных каталогов и индексов. Для остальных системных каталогов все нужные вам макросы с oid_symbol вы должны добавлять вручную.

Чтобы найти свободный OID для новой предварительно загружаемой строки, запустите скрипт src/include/catalog/unused_oids. Он выводит диапазоны неиспользуемых OID, включающие граничные значения (например, выведенная строка 45-900 означает, что OID с 45 по 900 включительно ещё не задействованы). В настоящее время для назначения вручную зарезервированы значения OID 1–9999; скрипт unused_oids просто просматривает заголовки каталогов и файлы .dat и проверяет, какие значения в них отсутствуют. Для поиска ошибок вы можете воспользоваться скриптом duplicate_oids. (Скрипт genbki.pl назначит OID всем строкам, которым он не был назначен вручную, и также выявит дублирующиеся OID во время компиляции.)

Выбирая значения OID для разрабатываемой модификации кода, которая скорее всего не будет принята сразу, рекомендуется использовать группу более-менее последовательных OID, начиная с некоторого случайного числа в диапазоне 8000—9999. Это уменьшит риск конфликтов OID с другими параллельными разработками. Чтобы сохранить диапазон 8000—9999 для использования в процессе разработки после того, как модификация кода вносится в основной репозиторий git, привнесённые с ней новые значения OID должны быть перенумерованы и заменены свободными номерами ниже этого диапазона. Обычно это будет происходить в конце каждого цикла разработки; таким образом, разом перенумеруются все OID, появившиеся в коде в течение завершающегося цикла. Для этого может применяться скрипт renumber_oids.pl. Этот скрипт также может быть полезен, если окажется, что некоторая ещё разрабатываемая модификация конфликтует с недавно внесёнными изменениями.

Из этого положения о возможной перенумерации значений OID, вносимых модификациями, следует, что выбранные в них изначально значения OID не должны считаться стабильными, пока эта модификация не будет включена в официальный выпуск. После выпуска мы не будем менять вручную назначенные OID объектов, так как это может повлечь самые разные проблемы совместимости.

Если скрипту genbki.pl требуется назначить OID элементу каталога, для которого OID не назначен вручную, он выбирает значение в диапазоне 10000—11999. Счётчик OID сервера получает значение 10000 на стадии начальной инициализации. Поэтому все объекты, создаваемые в ходе начальной инициализации, также получают OID в данном диапазоне. (Обычный механизм назначения OID позаботится о предотвращении конфликтов).

Объекты с OID ниже FirstUnpinnedObjectId (12000) считаются «закреплёнными», то есть они не могут быть удалены. (Существует небольшое количество исключений, жёстко заданных в IsPinnedObject().) Когда initdb доходит до фазы создания незакреплённых объектов, значение счётчика OID увеличивается до FirstUnpinnedObjectId. Таким образом, объекты, созданные на более поздних этапах initdb, например объекты, созданные при выполнении сценария information_schema.sql, не будут закреплёнными, а все объекты, известные genbki.pl, будут.

В процессе обычной работы сервер назначает OID от 16384 и выше. Тем самым гарантируется, что диапазон 10000—16383 свободен для значений OID, назначаемых автоматически скриптом genbki.pl или во время работы initdb. Эти автоматически назначаемые OID не считаются стабильными и могут меняться от одного кластера к другому.

74.2.3. Поиск OID по ссылке

Вообще говоря, из одной строки исходного каталога можно сослаться на другую, просто указав предопределённый OID целевой строки в поле ссылки. Однако политика проекта не приветствует такой подход, так как он провоцирует ошибки, а подобные ссылки сложны для восприятия и могут нарушиться при перенумерации OID. Поэтому в genbki.pl реализованы механизмы записи символических ссылок, работающие по следующим правилам:

  • Для использования символических ссылок в некотором столбце каталога требуется добавить указание BKI_LOOKUP(правило_поиска) в определение этого столбца, где правило_поиска ­­­­­­— имя целевого каталога, например pg_proc. Указание BKI_LOOKUP может быть добавлено к столбцам типа Oid, regproc, oidvector или Oid[]; в последних двух случаях поиск будет выполняться для каждого элемента массива.

  • Также допускается указание BKI_LOOKUP(encoding) для целочисленных столбцов, позволяющее обращаться к кодировкам символов (в настоящее время кодировкам не соответствуют OID в каталоге, но скрипту genbki.pl известен их набор).

  • Для некоторых столбцов каталога в качестве значений могут допускаться не только корректные ссылки, но и нули. Для таких столбцов вместо BKI_LOOKUP укажите BKI_LOOKUP_OPT. Это позволит задать 0 в качестве значения столбца. (Если столбец объявлен как regproc, вы можете написать - вместо 0.) За исключением этого особого случая, все записи в столбце BKI_LOOKUP должны содержать символьные ссылки. genbki.pl предупредит о нераспознанных именах.

  • Большинство типов объектов каталога представляются просто своими именами. Заметьте, что имена типов должны в точности соответствовать полям typname в записях pg_type; псевдонимы типов использовать нельзя, например, нельзя написать integer вместо int4.

  • Функция может быть представлена своим значением proname, если оно уникально среди записей pg_proc.dat (это работает как ввод значения типа regproc). В противном случае её нужно представить как proname(имя_типа_аргумента,имя_типа_аргумента,...), как в regprocedure. Имена типов аргументов должны записываться в точности так, как они фигурируют в поле proargtypes в pg_proc.dat. Не добавляйте в эту строку пробелы.

  • Операторы представляются в виде oprname(левый_тип,правый_тип), при этом имена типов записываются в точности так, как они фигурируют в полях oprleft и oprright в pg_operator.dat. (Вместо опущенного операнда унарного оператора записывается 0.)

  • Имена классов операторов и семейств операторов уникальны только в рамках определённого метода доступа, так что они представляются в виде имя_метода_доступа/имя_объекта.

  • Ни в одном из этих случаев не поддерживается указание схемы; все объекты, создаваемые на стадии начальной загрузки, будут принадлежать схеме pg_catalog.

Скрипт genbki.pl разрешает все символические ссылки при запуске и помещает в формируемый файл BKI обычные числовые OID. Таким образом, при начальной загрузке отпадает необходимость в разрешении имён.

Желательно добавить в определение столбцов с OID макрос BKI_LOOKUP или BKI_LOOKUP_OPT, даже если в каталоге нет исходных данных, требующих поиска для распознавания. Благодаря этому, genbki.pl сохранит существующую в системных каталогах связь по внешнему ключу. Эта информация используется в регрессионных тестах для выявления некорректных записей. Также обратите внимание на макросы DECLARE_FOREIGN_KEY, DECLARE_FOREIGN_KEY_OPT, DECLARE_ARRAY_FOREIGN_KEY и DECLARE_ARRAY_FOREIGN_KEY_OPT, позволяющие обозначить связи по внешним ключам, которые слишком сложны для BKI_LOOKUP (обычно внешние ключи со связями по нескольким столбцам).

74.2.4. Автоматическое создание типов массивов

Для большинства скалярных типов данных должны создаваться соответствующие типы массивов (то есть стандартный тип массива переменной длины с элементами скалярного типа, ссылка на который содержится в поле typarray скалярного типа pg_type). Как правило, скрипт genbki.pl может создать запись в pg_type для типа массива автоматически.

Для использования этой возможности достаточно записать array_type_oid => nnnn в поле метаданных записи pg_type для скалярного типа, указав OID, который будет использоваться для типа массива. После этого поле typarray можно не задавать, так как этот OID окажется в нём автоматически.

В качестве имени сгенерированного типа массива выбирается имя скалярного типа с добавленным спереди подчёркиванием. Другие поля в записи массива заполняются из определений BKI_ARRAY_DEFAULT(значение) в pg_type.h или, если соответствующего определения нет, копируются из скалярного типа. (Поле typalign заполняется особым образом.) Затем в полях typelem и typarray двух записей устанавливаются перекрёстные ссылки друг на друга.

74.2.5. Рецепты по редактированию файлов данных

Ниже приведены некоторые предложения по оптимальному решению некоторых распространённых задач при изменении файлов каталогов.

Добавление в каталог нового столбца со значением по умолчанию: Добавьте столбец в заголовочный файл с указанием BKI_DEFAULT(значение). Файл данных потребуется редактировать, только если в каких-либо существующих строках в добавленном поле должно быть не значение по умолчанию.

Указание значения по умолчанию для существующего столбца, который его не имел: Добавьте указание BKI_DEFAULT в заголовочный файл, а затем выполните make reformat-dat-files для удаления ставших избыточными записей поля.

Удаление столбца, со значением по умолчанию или без: Удалите столбец из заголовочного файла, а затем выполните make reformat-dat-files для удаления ставших избыточными записей поля.

Изменение или удаление существующего значения по умолчанию: Просто изменить заголовочный файл недостаточно, так как при этом текущие данные будут интерпретироваться некорректно. Сначала выполните make expand-dat-files, чтобы перезаписать в файлах данных все явно заданные значения по умолчанию, затем удалите или измените указания BKI_DEFAULT, и в завершение выполните make reformat-dat-files для повторного удаления избыточных полей.

Разовая массовая модификация: Скрипт reformat_dat_file.pl можно скорректировать для выполнения самых разных массовых модификаций. Просмотрев в нём блочные комментарии, вы найдёте место, куда можно вставить модифицирующий код. В следующем примере мы произведём объединение двух логических полей в pg_proc в символьном поле:

  1. Добавьте новый столбец со значением по умолчанию в pg_proc.h:

    +    /* see PROKIND_ categories below */
    +    char        prokind BKI_DEFAULT(f);
  2. Создайте на основе reformat_dat_file.pl новый скрипт, который вставит соответствующие значения «на лету»:

    -           # At this point we have the full row in memory as a hash
    -           # and can do any operations we want. As written, it only
    -           # removes default values, but this script can be adapted to
    -           # do one-off bulk-editing.
    +           # One-off change to migrate to prokind
    +           # Default has already been filled in by now, so change to other
    +           # values as appropriate
    +           if ($values{proisagg} eq 't')
    +           {
    +               $values{prokind} = 'a';
    +           }
    +           elsif ($values{proiswindow} eq 't')
    +           {
    +               $values{prokind} = 'w';
    +           }
  3. Запустите новый скрипт:

    $ cd src/include/catalog
    $ perl  rewrite_dat_with_prokind.pl  pg_proc.dat

    После этого в файле pg_proc.dat окажутся все три столбца, prokind, proisagg и proiswindow, хотя они будут фигурировать только в тех строках, где им присваиваются не значения по умолчанию, а любые другие значения.

  4. Удалите старые столбцы из pg_proc.h:

    -    /* is it an aggregate? */
    -    bool        proisagg BKI_DEFAULT(f);
    -
    -    /* is it a window function? */
    -    bool        proiswindow BKI_DEFAULT(f);
  5. Наконец, выполните make reformat-dat-files для удаления ненужных старых записей из pg_proc.dat.

Примеры кода, производящего массовые модификации, вы можете найти в скриптах convert_oid2name.pl и remove_pg_type_oid_symbols.pl, вложенных в сообщение: https://www.postgresql.org/message-id/CAJVSVGVX8gXnPm+Xa=DxR7kFYprcQ1tNcCT5D0O3ShfnM6jehA@mail.gmail.com