Skip to main content

Хост Postgres

Хост Postgres построен на базе фреймворка @morphcluster/core и предоставляет доступ к базе данных PostgreSQL в стиле, совместимом с Oracle (через адаптер). Он состоит из нескольких внутренних сервисов, каждый из которых выполняет свою роль: загрузка метаданных, выполнение запросов, управление пулом соединений, поддержка длинных транзакций, уведомления и тестовые инструменты.

Все сервисы наследуют ServiceRequire и регистрируются в ServiceHost с именем "Postgres". Конфигурация по умолчанию определена в src/config.mjs и может быть переопределена через CommonRegistry.

Сервис PgFunctions

Назначение: Загружает и хранит метаданные о функциях и процедурах PostgreSQL из системного каталога (pg_proc). Предоставляет методы для поиска функций по имени и пространству имён.

Наследует: ServiceRequire

Зависимости (requirements): отсутствуют (использует только config.common)

Конфигурация:

  • common.postgresUri – строка подключения к PostgreSQL
  • common.timeZone – часовой пояс (например, 'Europe/Moscow')

Методы (запросы):

Метод Параметры Возвращает Описание
reload {} void Перезагружает список функций и типов из БД
find {namespace, name} FuncRec | undefined Ищет функцию по полному имени (без учёта регистра)
findByName {name} FuncRec | undefined Ищет функцию по имени (без учёта регистра, только по имени)

Структура FuncRec:

{
  oid: number;          // OID функции
  name: string;         // Имя функции
  kind: string;         // 'f' - функция, 'p' - процедура
  nsp: string;          // Пространство имён (схема)
  result: string|null;  // Тип возвращаемого значения
  args: Array<{
    name: string;
    isOut: boolean;
    hasDefault: boolean;
    type: string | null;
  }>;
}

Жизненный цикл:

  • При старте автоматически вызывает reload().
  • При вызове reload() подключается к БД, запрашивает pg_type и список функций, разбирает аргументы с помощью ArgumentParser.

Особенности:

  • Игнорирует схемы pg_catalog и information_schema.
  • Для разбора аргументов используется встроенный парсер ArgumentParser, который обрабатывает стандартный вывод pg_get_function_arguments.

Сервис OraFunctions

Назначение: Предоставляет информацию о функциях PostgreSQL в нотации Oracle (адаптирует типы, имена, параметры). Используется клиентами, ожидающими Oracle-совместимый интерфейс.

Зависимости: ["PgFunctions"]

Конфигурация:
disableAutoload – если true, не вызывает reload() при старте (по умолчанию false).

Методы (запросы):

Метод Параметры Возвращает Описание
reload {} void Делегирует перезагрузку в PgFunctions.reload
list {} string[] Список имён схем (пакетов) в верхнем регистре
getPackage {packageName} {result: {id:-1, name, funcs[]}} Возвращает список функций пакета с именами в верхнем регистре
getFunc {packageName, funcName} {result: {name, overload, descr, params[]}} Возвращает метаданные одной функции (Oracle-стиль)

Структура параметра функции в ответе getFunc:

{
  pos: number;        // Позиция параметра
  name: string|null;  // Имя
  type: string;       // Oracle-тип (VARCHAR2, NUMBER, CURSOR и т.д.)
  out: boolean;       // Является ли OUT-параметром
  IN_OUT: "IN"|"OUT";// Строковое представление направления
  required: boolean;  // Обязательность (нет значения по умолчанию)
}

Особенности:

  • Преобразует PG-типы в Oracle-типы с помощью oraUtils.convertType.
  • Игнорирует функции из служебных схем.
  • Все имена схем и функций приводятся к верхнему регистру.

Сервис PgPool

Назначение: Управляет пулом простых (не транзакционных) соединений с PostgreSQL. Выполняет SQL-запросы и вызовы функций/процедур. Поддерживает очередь запросов с автоматическим переподключением и записью ошибок в лог.

Зависимости: ["PgFunctions", "RegistryHelper"]

Конфигурация:

  • common.postgresUri – строка подключения
  • common.timeZone – часовой пояс
  • pgQueryPool.connectionCount – количество соединений в пуле (из CommonRegistry, схема registrySchema)

Методы (запросы):

Метод Параметры Возвращает Описание
reconnect {} void Пересоздаёт пул соединений (закрывает старые, открывает новые согласно connectionCount)
execSql {sql, values, session?, noLog?} Promise<Array<{paramName, type, value}>> Выполняет сырой SQL-запрос
execFunc {namespace, funcName, params, count?, offset?, session?, noLog?} Promise<Array<{paramName, type, value}>> Выполняет функцию/процедуру по метаданным из PgFunctions

Дополнительно:

  • getState() – не запрос, а метод для получения состояния пула (возвращает массив объектов с id и status каждого соединения).

Жизненный цикл:

  • При start() создаёт подписку на изменения реестра через RegistryHelper.onDataChanged, при изменении пересоздаёт пул.
  • При stop() отключает все соединения и очищает таймер переподключения.

Внутреннее устройство:

  • Пул состоит из объектов PgConnection.
  • Запросы помещаются в очередь queue и обрабатываются последовательно с помощью processQueue().
  • При ошибке соединение переподключается автоматически (до 3 попыток).
  • Для установки контекста пользователя вызывается carabimeta.set_user_id(userId) перед каждым запросом.
  • Ошибки запросов логируются в БД через функцию logs.write_error_log.

Формат возвращаемых данных:

  • PgConnection возвращает результаты в виде массива {paramName, type, value}.
  • Типы данных преобразуются через PgFormats.formatValue().

Сервис PgTransactions

Назначение: Предоставляет механизм длинных транзакций (long transactions) с состоянием, возможностью выполнения нескольких запросов в одной транзакции, коммитом/откатом и фетчем курсоров.

Зависимости: ["RegistryHelper", "DbServices", "PgFunctions"]

Конфигурация (из CommonRegistry):

  • PgTransactions.cleanTimeout – время в мс, через которое закрытые транзакции удаляются из пула (по умолчанию 600000)
  • PgTransactions.idleTimeout – максимальное время простоя транзакции в мс (0 – без ограничения, по умолчанию 60000)

Методы (запросы):

Метод Параметры Возвращает Описание
create {noUser?, session?} {trxId: number} Создаёт новую транзакцию, возвращает её ID
execSql {trxId?, sql, values, session?} {trxId} Выполняет SQL в новой (если trxId не указан) или существующей транзакции
execFunc {trxId?, namespace, funcName, params, session?} {trxId} Выполняет функцию из PgFunctions в транзакции
getExecResult {trxId} Array<Result> Ожидает завершения выполнения и возвращает результаты последнего запроса в транзакции
fetch {trxId, count} {rows} Извлекает строки из курсора
commit {trxId, keep?} void Фиксирует транзакцию; если keep=true, сразу начинает новую транзакцию
rollback {trxId} void Откатывает транзакцию
poolStatus {} {lastOltId, connections[]} Возвращает состояние пула транзакций

Внутреннее устройство:

  • Использует пул PGTPool, который управляет набором PGTransaction.
  • Каждая транзакция (PGTransaction) проходит через состояния: created → connecting → ready → executing → executed → (fetching) → closing → closed.
  • Транзакция начинается с BEGIN, выполняет запросы, и завершается COMMIT или ROLLBACK.
  • Поддерживается работа с курсорами (refcursor): после выполнения функции, возвращающей курсор, можно вызывать fetch.
  • При возникновении ошибки в транзакции, она сохраняется в error, и может быть получена через getExecResult.
  • Ошибки логируются в logs.write_error_log аналогично PgPool.
  • Устаревшие (закрытые) транзакции периодически удаляются из пула.

Формат результата getExecResult:

[
  {
    paramName: string;
    type: string;       // PG-тип
    value: any;         // для скалярных
    columns?: [string, string][]; // для курсоров
  }
]

Важно: Все методы, изменяющие состояние транзакции, являются асинхронными и не блокируют вызывающий поток (кроме getExecResult и fetch, которые ожидают готовности результата).


Сервис PgTools

Назначение: Утилита для генерации заготовки функции, возвращающей TABLE, на основе существующей функции, возвращающей refcursor.

Зависимости: отсутствуют (только config)

Методы (запросы):

Метод Параметры Возвращает Описание
funcTableFromRefcursor {funcFrom, funcTo} Array<{name, type, modifier, size, tableID}> Анализирует структуру курсора, возвращает метаданные колонок и создаёт новую функцию с RETURNS TABLE

Параметры:

  • funcFrom – строка вида schema.func_name(args) (например, public.my_func(123))
  • funcTo – полное имя новой функции (по умолчанию schema.func_name_table)

Пример использования:

{
  "funcFrom": "public.my_cursor_func(42)",
  "funcTo": "public.my_table_func"
}

Создаст функцию public.my_table_func(...) RETURNS TABLE(...) на основе структуры курсора.

Особенности:

  • Выполняет FETCH 0 для получения структуры курсора без перемещения.
  • Автоматически преобразует типы через pg_type.
  • Требует прав администратора (needAdmin: true).

Сервис PgNotify

Назначение: Обеспечивает механизм подписки на асинхронные уведомления PostgreSQL (LISTEN/NOTIFY). Уведомления доставляются через событие onNotify.

Зависимости: []

Конфигурация:
common.postgresUri – строка подключения.

Методы (запросы):

Метод Параметры Возвращает Описание
isConnected {} {result: boolean} Проверяет, установлено ли соединение с БД для нотификаций
getSubscriptions {} {result: string[]} Возвращает список активных подписок (имен каналов)
subscribe {channelName} void Подписывается на канал уведомлений
unsubscribe {channelName} void Отписывается от канала

События:

Событие Параметры Описание
onNotify {channel, payload} Вызывается при получении уведомления. payload уже распарсен из JSON.

Жизненный цикл:

  • При старте подключается к PostgreSQL через PgNotifyConnection и восстанавливает все активные подписки.
  • При остановке корректно отключается.
  • Встроенный таймер (ReconnectTimer) каждые 5 секунд проверяет соединение и переподключается при обрыве.

Особенности:

  • Идентификаторы каналов экранируются для безопасного использования в SQL.
  • Уведомления автоматически парсятся из JSON (если возможно), иначе передаётся строка.

Сервис OraQueries

Назначение: Выполняет запросы к PostgreSQL в стиле Oracle. Преобразует именованные параметры в позиционные, конвертирует типы результатов, валидирует вызовы через DbServices.

Зависимости: ["PgPool", "DbServices"]

Методы (запросы):

Метод Параметры Возвращает Описание
exec {queryName, params?, count?, offset?} {results: [...]} Основной метод: парсит queryName в формате package.funcName и вызывает execFunc
execFunc {package, func, params?, count?, offset?, session?} {results: [...]} Выполняет функцию, предварительно получив SQL-текст через DbServices.getOraSql
execFuncInner {package, func, params?, count?, offset?, session?} {results: [...]} Выполняет функцию напрямую через PgPool, без проверки через DbServices (требует needAdmin: true)
execSql {sql, params?, session?} {results: [...]} Выполняет произвольный SQL (административный доступ)
resetUser {} (не реализован) Планируется сброс сессии текущего пользователя
resetAllUsers {} (не реализован) Планируется сброс всех сессий

Возвращаемое значение:

{
  results: Array<{
    paramName: string;   // имя выходного параметра (в верхнем регистре)
    type: string;        // Oracle-тип: "VARCHAR2", "NUMBER", "CURSOR", "CLOB", "DATE"...
    value: any;          // значение, для курсоров - {columns, list}
  }>
}

Особенности:

  • Автоматически преобразует именованные параметры (:name) в позиционные ($1) с помощью oraUtils.convertNameToPos.
  • Конвертирует PG-типы в Oracle-типы (convertType).
  • Для курсоров преобразует структуру через convertColumns.
  • Логирует результат с ограничением размера (если задан limitLogger).
  • В случае отсутствия функции в PgFunctions, записывает информацию о недостающем вызове в logs.write_error_log и выбрасывает ComplexError.

Сервис OraLongTransactions

Назначение: Предоставляет Oracle-совместимый интерфейс для длинных транзакций. Является обёрткой над PgTransactions, адаптируя имена методов и параметры.

Зависимости: ["PgTransactions", "DbServices"]

Методы (запросы):

Метод Параметры Возвращает Описание
create {noUser?, session?} {oltId} Создаёт новую транзакцию (аналог PgTransactions.create)
execFunc {oltId?, package, func, params?, session?} {oltId} Выполняет функцию пакета (перед выполнением проверяет через DbServices)
execSql {oltId?, SQL, rawParams?, session?} {oltId} Выполняет произвольный SQL с преобразованием именованных параметров
getExecResult {oltId} Array<Result> Ожидает результат транзакции
fetch {oltId, count} {rows} Извлекает строки из курсора
commit {oltId, keep?} void Фиксирует транзакцию
rollback {oltId} void Откатывает транзакцию
poolStatus {} (состояние пула) Делегирует в PgTransactions.getStatus

Особенности:

  • Параметры execFunc принимают package и func вместо namespace/funcName.
  • При выполнении SQL через execSql, параметры могут быть переданы в нотации Oracle (:paramName), они автоматически конвертируются в позиционные через oraUtils.convertNameToPos.
  • Все выходные параметры и курсоры приводятся к Oracle-типам.

Сервис OraQueriesPool

Назначение: Управление пулом обычных (не транзакционных) сессий OraQueries. Предоставляет административные методы для мониторинга и управления.

Зависимости: ["PgPool", "DbServices"]

Методы (запросы):

Метод Параметры Возвращает Описание
state {} {result: [{name, pid, sessionsCount, workedSessions[], ...}]} Возвращает состояние пула (основано на PgPool.getState())
recreate {} ошибка Планируется пересоздание пула, в текущей версии не реализован
brakeQuery {oraSid} ошибка Планируется прерывание запроса по идентификатору сессии

Формат state:

{
  "result": [
    {
      "name": "main",
      "pid": 0,
      "sessionsCount": 4,
      "workedSessions": [{"id": "pool_0", "status": "exec"}, ...],
      "queryQueue": [],
      "killerSession": null,
      "usage": { ... }
    }
  ]
}

Особенности:

  • Фактически является административной панелью для PgPool.
  • Методы recreate и brakeQuery пока не реализованы (выбрасывают ошибку).

Общая архитектура и зависимости

graph TD
    subgraph "Внешние сервисы"
        DbServices[DbServices]
        CommonRegistry[CommonRegistry]
        RegistryHelper[RegistryHelper]
    end

    subgraph "Сервисы PostgreSQL"
        PgFunctions --> |загрузка метаданных| PostgreSQL
        OraFunctions --> PgFunctions
        PgPool --> PgFunctions
        PgPool --> RegistryHelper
        PgTransactions --> PgFunctions
        PgTransactions --> RegistryHelper
        PgTransactions --> DbServices
        PgTools --> PostgreSQL
        PgNotify --> PgNotifyConnection --> PostgreSQL
        OraQueries --> PgPool
        OraQueries --> DbServices
        OraLongTransactions --> PgTransactions
        OraLongTransactions --> DbServices
        OraQueriesPool --> PgPool
        OraQueriesPool --> DbServices
    end

    RegistryHelper --> CommonRegistry

Все соединения с PostgreSQL используют строку подключения из конфигурации common.postgresUri. Настройки пулов и таймаутов управляются через CommonRegistry, изменения вступают в силу после перезагрузки пула (для PgPool — автоматически при изменении схемы).


Конфигурация по умолчанию

Файл config.mjs:

{
  "NatsConnection": {
    "queue": "pgqueries",
    "timeout": 60000
  },
  "NatsPublisher": {
    "interval": 60000
  }
}

Дополнительные параметры (из config.common):

  • postgresUri – строка подключения к PostgreSQL.
  • timeZone – часовой пояс для сессий (например, 'Europe/Moscow').

Примечания по использованию

  • Большинство сервисов требуют наличия DbServices для проверки и выполнения хранимого кода. DbServices – это внешний сервис, который должен быть доступен в кластере (предоставляет запросы getOraSql и др.).
  • Для работы с длинными транзакциями используйте OraLongTransactions, а не PgTransactions напрямую, чтобы обеспечить совместимость с Oracle-клиентами.
  • Административные методы защищены флагом needAdmin: true, который требует аутентифицированной сессии с правами администратора.
  • Все ошибки базы данных логируются в logs.write_error_log (если функция доступна и не отключена флагом noLog).

Данный хост является ключевым компонентом для миграции с Oracle на PostgreSQL, обеспечивая прозрачную работу приложений, написанных под Oracle.