Skip to main content

Сервис LegacyXml2Select

LegacyXml2Select – это сервис, наследующий ServiceRequire, который предоставляет возможность парсить XML-запросы, сформированные в стиле «формальных выборок», и генерировать соответствующие SQL SELECT-запросы к документам в БД. Основное назначение – обеспечить обратную совместимость со старыми механизмами поиска.

Сервис состоит из нескольких компонентов:

  • LegacyXml2Select – фасад, предоставляющий внешние методы.
  • LegacyXml2SelectDb – сервис‑помощник для доступа к базе данных (получение метаданных, выполнение функций).
  • LegacyXml2SelectInnerPg – ядро парсинга XML и построения SQL.
  • PropertyBuilder – построитель условий для свойств документов.
  • MacroParser – парсер макросов (функции, литералы).
  • SqlQuery / WhereCondition – утилиты для программного конструирования SQL-выражений и оптимизации WHERE-условий.

Все компоненты располагаются в одном модуле и экспортируются как единое целое.


Сервис LegacyXml2Select (index.mjs)

Наследует: ServiceRequire (из @morphcluster/core)
Назначение: Принимает XML‑строку, параметры режима запроса и прав доступа, преобразует её в объект SqlQuery (или сразу в SQL‑строку) и возвращает результат.

Зависимости

  • QueriesHelper – обязательный (указан в requirements), предоставляет доступ к выполнению запросов к PostgreSQL.
  • LegacyXml2SelectDb – обязательный (добавляется как вложенный сервис в конструкторе).

Схема запросов (service-schema.mjs)

Файл схемы содержит описание трёх запросов:

  • parse – требует xml (string), возвращает объект {} (в коде соответствует метод parseToObj, см. Примечание)
  • parseDb – аналогичен parse, но с дополнительным взаимодействием с БД (в коде метода parseDb нет; вероятно, метод parseToSql использует БД)
  • macroParse – требует macro (string), возвращает дерево разбора макроса

Примечание: В текущей реализации класс LegacyXml2Select определяет методы parseToObj, parseToSql, macroParse. Методы parse и parseDb в схеме, по-видимому, являются альтернативными или устаревшими именами. Рекомендуется обновить схему для однозначности.

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

Параметр config передаётся в конструктор и доступен через this.config. Специфических опций не требуется.

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

  • Конструктор создаёт и регистрирует в том же хосте экземпляр LegacyXml2SelectDb (как сервис с именем "LegacyXml2SelectDb").
  • При старте (start) вызывает super.start().

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

parseToObj({ session, xml, queryMode=0, permissions=1 })

Преобразует XML в объект SqlQuery (без построения финального SQL‑текста).

Параметры:

  • session (объект, опционально) – должен содержать userId, используется для фильтрации прав.
  • xml (string) – XML‑строка запроса.
  • queryMode (number, по умолчанию 0) – режим запроса:
    • 0 – полный запрос (выбирает поля документа),
    • 1 – подсчёт количества (COUNT),
    • 2 – только идентификаторы документов,
    • 3‑6 – специальные режимы (логика меняется).
  • permissions (number, по умолчанию 1) – уровень проверки прав доступа (-1 – без проверки, 1 – стандартная).

Возвращает: экземпляр SqlQuery.

parseToSql({ session, xml, queryMode=0, permissions=1 })

Выполняет parseToObj, а затем вызывает sql.build(), возвращая готовый SQL‑запрос.

Параметры: те же, что у parseToObj.

Возвращает: строку SQL.

macroParse({ macro })

Парсит строку‑макрос в абстрактное синтаксическое дерево.

Параметры:

  • macro (string) – строка, содержащая вызов функции, литерал или идентификатор.

Возвращает: объект с полем tree – AST макроса (например, { type: 'function', value: 'NOW', args: [] }).


Сервис LegacyXml2SelectDb (LegacyXml2SelectDb.mjs)

Наследует: ServiceRequire
Назначение: Предоставляет низкоуровневые методы для взаимодействия с БД, необходимые при разборе свойств документов (метаданные, справочники, функции).

Зависимости

  • QueriesHelper – обязательный.

Методы

Каждый метод получает log и userId для логирования и контекста прав.

Метод Параметры Возвращаемое значение Описание
getPropKind(log, userId, docpropId) docpropId: number Тип свойства (строка?) Получает «kind» свойства документа (через пакет CSP_XML2SELECT.GET_PROP_KIND).
getPropKindType(log, userId, docpropId) docpropId: number Число – тип kind Получает числовой тип свойства (CSP_XML2SELECT.GET_PROP_KIND_TYPE).
listClassifierResources(log, userId, docPropValue) docPropValue: string Массив строк‑описаний ресурсов Иерархический поиск ресурсов классификатора по значению.
getUsername(log, userId) Строка (ФИО) или null Возвращает полное имя текущего пользователя.
getRoleId(log, userId) Число – ID роли Вызывает documents.GET_ROLE_ID().
getRoleName(log, userId) Строка или null Название роли из справочника.
getDepartId(log, userId) Строка или null Идентификатор отдела пользователя.
execFunc(log, userId, dbFunc) dbFunc: string – имя функции (например, MY_FUNC()) Результат выполнения Выполняет SQL‑функцию через SELECT.
execDate(log, userId, dateFunc) dateFunc: string – выражение даты (например, SYSDATE) Строка даты в формате DD.MM.YYYY^HH24:MI:SS или null Преобразует выражение даты в строку.
getDocPropKind(log, userId, docpropId) docpropId: number Значение docprop_kind или null Возвращает тип поля свойства (из model_documents.doc_kind_properties).
listEvents(log, userId, docKindId) docKindId: number Массив ID статусов Список глобальных и связанных с видом документа статусов (событий).
getSelectLevel(log, userId, docpropId) docpropId: number Число (уровень зависимости) Уровень вложенности зависимостей свойства (CSP_XML2SELECT.GET_SELECT_LEVEL).
getVocabIdByDocPropId(log, userId, docpropId) docpropId: number ID словаря или null Получает идентификатор словаря, привязанного к свойству.
listVocabCodes(log, userId, vocabId, codes) vocabId: number, codes: string[] Массив кодов Возвращает существующие в словаре коды (точное совпадение).
listVocabCodesHierarchical(log, userId, vocabId, code) vocabId: number, code: string Массив кодов (включая дочерние) Иерархический поиск кодов словаря (родитель‑потомок).

Все методы используют QueriesHelper.query() или querySql() для выполнения запросов.


Класс LegacyXml2SelectInnerPg

Файл: LegacyXml2SelectInnerPg.mjs
Назначение: Содержит основную логику преобразования XML в SQL‑запрос для PostgreSQL. Использует экземпляр LegacyXml2SelectDb для получения метаданных, PropertyBuilder для условий свойств, и SqlQuery / WhereCondition для построения запроса.

Конструктор

constructor(Xml2SelectDb)
  • Xml2SelectDb – экземпляр LegacyXml2SelectDb (или совместимый объект).

Создаёт экземпляр PropertyBuilder, инициализирует пустой SqlQuery (mainQuery), и устанавливает начальные значения queryMode, permissions, userId, log.

Методы

xml2Select(xml, queryMode = 0, permissions = 0, userId, log)

Главный публичный метод. Парсит XML, обходит его формальную часть, конструирует запрос и возвращает SqlQuery.

Параметры:

  • xml (string) – XML‑строка с корневым элементом <query><formal dockind_id="...">...</formal></query>.
  • queryMode – режим запроса.
  • permissions – уровень проверки прав.
  • userId – идентификатор пользователя.
  • log – объект логгера.

Возвращает: SqlQuery.

Внутренние вспомогательные методы

  • #parseXml(xml) – парсит XML‑строку с помощью fast-xml-parser. Добавляет XML‑декларацию, если отсутствует. Возвращает JS‑объект.
  • #getRefs(xmlRef) – извлекает атрибуты ссылки (docprop_id, referencing, dockind_id и т.д.).
  • #buildReferenceSubquery({ refPropId, referencing, docKindId, condition, counter }, level) – создаёт подзапрос EXISTS для проверки ссылок между документами. Добавляет условия прав, если необходимо.
  • #buildStatusCondition(statList, level) – формирует условие IN для фильтрации по статусам.
  • #parseFormalReference(xmlRef, parentDockindId, operName, level, outerJoin) – обрабатывает одну ссылку в формальном блоке, рекурсивно обходит вложенные ссылки, свойства, статусы, операции. Возвращает WhereCondition.
  • #parseFormal(xmlObj, docKindId, level, outerJoin) – обходит формальный блок XML и возвращает агрегированное WhereCondition, объединяя все ссылки, свойства, статусы и операции через AND/OR.

Все приватные методы активно используют PropertyBuilder для построения условий свойств.


Класс PropertyBuilder

Файл: PropertyBuilder.mjs
Назначение: Строит условия WHERE для свойств документов на основе их XML‑описания и метаданных, полученных из Xml2SelectDb.

Конструктор

constructor(Xml2SelectDb)

Принимает экземпляр LegacyXml2SelectDb.

Основной метод

buildPropCondition(log, userId, xmlProp, docKindId, level)

Возвращает Promise<WhereCondition> – условие для одного свойства.

Параметры:

  • log, userId – стандартные.
  • xmlProp – XML‑узел свойства (содержит @_docprop_id, @_type, @_condition и т.д.).
  • docKindId – идентификатор вида документа.
  • level – уровень вложенности в дереве ссылок.

Логика работы:

  1. Извлекает метаданные свойства через #getPropFromXml.
  2. Определяет SQL‑оператор по атрибуту condition (например, 'e''=' / 'IN').
  3. Обрабатывает макросы в значениях (@USERID, @DATE и др.) через #processMacros.
  4. Если docPropId === -2, строит условие для фильтрации по ID документов (#buildDocIdCondition).
  5. Для остальных свойств создаёт подзапрос EXISTS к value_documents.doc_properties, присоединяет зависимости согласно selectLevel.
  6. Добавляет специфичные условия в зависимости от типа свойства (kindType):
    • Классификаторы (docPropKindId === 20),
    • Словари (kindType === 4),
    • Скалярные типы (строки, числа, даты) – через #buildScalarConditions.
  7. Возвращает WhereCondition типа 'exists', содержащий собранный подзапрос.

Внутренние вспомогательные методы описаны в исходном коде.


Класс MacroParser

Файл: MacroParser.mjs
Назначение: Простейший рекурсивный парсер для разбора макрос‑строк, содержащих вызовы функций, числа, строки и идентификаторы.

Конструктор

constructor(input: string)

Инициализирует позицию и текущий символ.

Метод parse()

Парсит выражение и возвращает AST‑узел.

Возвращаемый объект (примеры):

  • { type: 'string', value: 'abc' }
  • { type: 'number', value: 123 }
  • { type: 'identifier', value: 'USER' }
  • { type: 'function', value: 'NOW', args: [ ... ] }

Поддерживает вложенные вызовы: FUNC(1, 'x', OTHER(2)).


Классы SqlQuery и WhereCondition

Файл: SqlQuery.mjs
Назначение: Программное построение SQL‑запросов с удобным API и оптимизацией условий.

WhereCondition

Конструктор: new WhereCondition(type = 'and', param = null)

Тип type Назначение param
'simple' Строка SQL‑выражения (напр., "age > 18")
'exists' Экземпляр SqlQuery (подзапрос)
'and' / 'or' Массив WhereCondition[] (опционально)
'not' Единственный WhereCondition

Основные методы:

  • add(condition) – добавить условие в группу.
  • addSimple(expr) – добавить простое условие.
  • addExists(subquery), addNotExists(subquery) – EXISTS / NOT EXISTS.
  • addNot(condition) – добавить отрицание.
  • addOr(conditions), addAnd(conditions) – добавить подгруппу.
  • optimize() – возвращает оптимизированное дерево (удаление избыточных вложений, двойного отрицания, TRUE/FALSE свёртки).
  • build(options) – рекурсивно генерирует SQL‑строку условия.

SqlQuery

Представляет полный SQL‑запрос.

Свойства:

  • query.select – массив выражений для SELECT.
  • query.from – массив таблиц/подзапросов.
  • query.joins – массив JOIN’ов.
  • query.orderBy, query.groupBy, query.limit, query.offset – соответствующие части запроса.
  • where – корневой WhereCondition (по умолчанию пустая 'and' группа).

Методы (возвращают this для цепочек):

  • select(exprs) – добавить колонки.
  • from(table, alias?) – добавить таблицу или SqlQuery как подзапрос.
  • innerJoin(...), leftJoin(...), addJoin(type, ...) – присоединения (поддерживают LATERAL).
  • orderBy(columns), groupBy(columns), limit(limit, offset?) – сортировка, группировка, лимит.
  • build(options) – собирает SQL‑строку.
  • buildPretty(options) – то же с форматированием.

Опции build:

  • pretty (boolean, по умолчанию false) – включить отступы.
  • indentSize (number, по умолчанию 2) – размер отступа.
  • isSubquery (boolean) – внутренний флаг для правильной обработки подзапроса.
  • indent (number) – текущий уровень вложенности.

Взаимодействие компонентов

  1. LegacyXml2Select получает XML и через LegacyXml2SelectInnerPg.xml2Select() создаёт объект SqlQuery.
  2. Внутри xml2Select XML преобразуется в объект, определяется dockind_id, формируются начальные SELECT/FROM/WHERE в зависимости от queryMode.
  3. Вызывается #parseFormal, который обходит все ссылки, свойства, статусы и операции.
  4. Для каждого свойства вызывается PropertyBuilder.buildPropCondition, который может выполнять запросы к БД через LegacyXml2SelectDb для получения метаданных (например, getPropKind, listClassifierResources, listVocabCodesHierarchical). Результат добавляется в дерево WhereCondition.
  5. После обхода вызывается mainQuery.where.optimize() для упрощения, и готовый SqlQuery возвращается.
  6. При необходимости клиент может вызвать query.build() для получения SQL‑строки или использовать объект SqlQuery для дальнейших модификаций.

Примечания и ограничения

  • LegacyXml2SelectInnerPg содержит закомментированную логику для HAVING с подсчётом (counter), которая выбрасывает исключение. Это указывает на незавершённую реализацию.
  • Метод #parseFormalReference имеет неполную реализацию для outerJoin в режимах 4‑6 (возвращает пустое WhereCondition вместо корректного условия JOIN).
  • Схема сервиса (service-schema.mjs) расходится с реальными методами: parse и parseDb не реализованы, вместо них используются parseToObj и parseToSql. Рекомендуется привести схему в соответствие.
  • LegacyXml2SelectDb использует прямые SQL‑запросы через QueriesHelper, предполагая наличие определённых хранимых процедур и пакетов (CSP_XML2SELECT, documents, vocabs).
  • Классификаторы и словари требуют корректной настройки справочников в БД.
  • Макросы поддерживают ограниченный набор подстановок; для новых переменных потребуется расширение #processMacros в PropertyBuilder.

Пример использования (псевдокод)

const host = new ServiceHost('test');
const legacySvc = new LegacyXml2Select(host, {});
await host.start(log);

// Предположим, что QueriesHelper настроен и БД доступна
const sql = await legacySvc.sendRequest('parseToSql', {
  session: { userId: 123 },
  xml: '<query><formal dockind_id="5"><property docprop_id="10" condition="e" value="test"/></formal></query>',
  queryMode: 0,
  permissions: 1
}, null, log);
console.log(sql); // Сгенерированный SQL SELECT ...