Skip to content

fix(sdbl-hir): проверка полей, таблиц и параметров виртуальных таблиц регистров - #422

Open
flamber-dev wants to merge 2 commits into
itrous:developfrom
flamber-dev:pr/register-virtual-table-checks
Open

flamber-dev wants to merge 2 commits into
itrous:developfrom
flamber-dev:pr/register-virtual-table-checks

Conversation

@flamber-dev

Copy link
Copy Markdown
Contributor

Проблема

Проверка запроса (UnknownFieldInQuery, query validate) сверяет с метаданными только таблицу верхнего уровня.
На виртуальных таблицах регистров она молчит, и ответ «замечаний нет» выглядит как полная проверка, хотя
платформа такой запрос не выполнит:

ВЫБРАТЬ
	Т.Ресурс2Остаток КАК Остаток,
	Т.Реквизит1 КАК Реквизит,          // реквизитов у таблицы Остатки нет
	Т.НетТакогоПоля123 КАК П
ИЗ
	РегистрНакопления.Остатки1.Остатки(&Период, ) КАК Т

На develop (f63a2eb) молча проходят:

  • несуществующее поле виртуальной таблицы регистра накопления (Остатки, Обороты, ОстаткиИОбороты) и среза
    регистра сведений (СрезПоследних, СрезПервых);
  • любое поле регистра бухгалтерии, включая главную таблицу: имена его полей не совпадают с именами метаданных
    (небалансовое измерение или ресурс корреспондирующего регистра называется по сторонам - <Измерение>Дт,
    <Ресурс>Кт; число полей Субконто<N> задаёт план счетов), а свойства Балансовый, ПоддержкаКорреспонденции,
    ПланСчетов и МаксКоличествоСубконто не читались;
  • несуществующая виртуальная таблица (РегистрБухгалтерии.Бухгалтерия1.НетТакойТаблицы(...),
    РегистрСведений.Сведения1.Остатки(...), Остатки у оборотного регистра накопления);
  • лишний аргумент виртуальной таблицы (условие, разбитое запятой вместо И) и условие в позиции, которая принимает
    значение (условие по счёту в позиции Субконто): параметры читаются строго по позиции.

Обратная сторона - ложные находки. Виртуальная таблица определялась по последнему сегменту имени без учёта корня,
поэтому временная таблица Остатки / Обороты и табличная часть Документ.X.Остатки получали
VirtualTableCallWithoutParameters и JoinWithVirtualTable, хранимые таблицы РегистрБухгалтерии.X.Субконто и
<Объект>.Изменения - тоже; при этом ОборотыДтКт и ДвиженияССубконто не распознавались вовсе (в списке стояли
имена с опечатками). Аргумент системы компоновки (СрезПоследних({&КонецПериода}, )) считался отсутствующим.

Что меняется

Два коммита.

feat(bsl-metadata) - читаются свойства, от которых зависит форма таблиц регистра бухгалтерии: Balance измерения и
ресурса, Correspondence и ChartOfAccounts регистра бухгалтерии, MaxExtDimensionCount плана счетов. Отсутствие
свойства - None («неизвестно»), не false.

fix(sdbl-hir):

  • модели полей виртуальных таблиц. Регистр накопления: измерения + производные ресурсов (Остаток, Оборот,
    Приход, Расход, НачальныйОстаток, КонечныйОстаток) + разбивка по периоду (ПериодМесяц, ...). Срез регистра
    сведений: Период, измерения, ресурсы, реквизиты. Регистр бухгалтерии (новый lower/register_tables.rs): главная
    таблица и Остатки, Обороты, ОборотыДтКт, ОстаткиИОбороты, ДвиженияССубконто. Имена, которые есть
    только при некоторых аргументах вызова или настройках, помечены provisional (правило неизвестного поля на них молчит,
    правило неоднозначности их не считает) - механизм, который в develop уже есть. Без плана счетов семейство
    Субконто<N> не ограничить - таблица остаётся неполной, и правило молчит;
  • общие реквизиты - как в главной таблице develop: разделитель - колонка каждой таблицы регистра, обычный общий
    реквизит - колонка таблиц, где есть реквизиты регистра; открытый набор общих реквизитов (common_attributes_open)
    оставляет таблицу непроверяемой;
  • классификация с учётом корня: суффикс - виртуальная таблица, только если вид регистра такую таблицу имеет
    (register_table_kinds); хранимые Субконто и Изменения параметров не принимают; неизвестный третий сегмент у
    регистра - QueryToMissingMetadata (кроме регистра расчёта: имена его таблиц баз и перерасчётов задаёт
    конфигурация);
  • новая диагностика InvalidVirtualTableParameters (Error / Blocker): аргументов больше, чем параметров у
    таблицы, или условие в позиции значения. Позиции берутся по запятым самого вызова; вызов, разобранный с ошибкой
    (например, шаблон-макрос), пропускается. Списки параметров - для таблиц регистров сведений, накопления и
    бухгалтерии; вид регистра виден из текста, метаданные не нужны. Документация docs/{ru,en};
  • разрешение голых имён, которое вскрыла точная типизация. Условие в параметрах виртуальной таблицы говорит
    только о полях этой таблицы (непрозрачный кадр), а УПОРЯДОЧИТЬ ПО и ИТОГИ сначала ищут имя среди выходных
    колонок запроса. Иначе две соединённые таблицы остатков давали ложную неоднозначность Счет. Выходная колонка
    владеет именем при любом своём типе: колонка неизвестного типа (параметр) или сама неоднозначная колонка не должна
    отправлять имя дальше к источникам - так получалась неоднозначность с пустым списком таблиц
    (Возможные таблицы: ).

Доказательство

Тесты - crates/sdbl-hir/src/lower/register_table_tests.rs (25 тестов, синтетические регистры Остатки1,
Обороты1, Сведения1, Бухгалтерия1 на плане счетов План1), плюс в ide-diagnostics:
invalid_virtual_table_parameters::tests::* (4) и unknown_field_in_query::tests::positive_unknown_field_on_register_slice
(бывший negative_register_virtual_table_gate_off, который закреплял молчание).

На develop (f63a2eb), только файлы тестов:

   Compiling sdbl-hir v0.3.2 (...\crates\sdbl-hir)
thread 'lower::register_table_tests::accumulation_virtual_tables' panicked at crates\sdbl-hir\src\lower\register_table_tests.rs:163:5:
assertion `left == right` failed: [РегистрНакопления.Остатки1.Остатки(&Период, )] every missing field must be reported
  left: []
 right: ["НетТакогоПоля123", "Период", "Реквизит1", "Ресурс2", "Ресурс2Оборот"]
thread 'lower::register_table_tests::information_register_slice' panicked at crates\sdbl-hir\src\lower\register_table_tests.rs:163:5:
assertion `left == right` failed: [РегистрСведений.Сведения1.СрезПоследних(&Период, )] every missing field must be reported
  left: []
 right: ["НетТакогоПоля123", "Ресурс1Остаток"]
thread 'lower::register_table_tests::accounting_balance_table' panicked at crates\sdbl-hir\src\lower\register_table_tests.rs:163:5:
assertion `left == right` failed: [РегистрБухгалтерии.Бухгалтерия1.Остатки(&Период, Счет = &Счет, , )] every missing field must be reported
  left: []
 right: ["НетТакогоПоля123", "Период", "Ресурс1Оборот", "Субконто4", "СчетДт"]
thread 'lower::register_table_tests::a_table_the_register_does_not_have_is_missing_metadata' panicked at crates\sdbl-hir\src\lower\register_table_tests.rs:392:9:
assertion `left == right` failed: ВЫБРАТЬ Т.Счет КАК Счет ИЗ РегистрБухгалтерии.Бухгалтерия1.НетТакойТаблицы(&Период, , , ) КАК Т
  left: []
 right: ["РегистрБухгалтерии.Бухгалтерия1.НетТакойТаблицы"]
thread 'lower::register_table_tests::a_stored_table_named_like_a_virtual_one_is_not_a_virtual_table_call' panicked at crates\sdbl-hir\src\lower\register_table_tests.rs:588:9:
assertion `left == right` failed: ВЫБРАТЬ Т.Реквизит1 КАК Р ИЗ Документ.Документ1.Остатки КАК Т
  left: [("VirtualTableCallWithoutParameters", "Документ.Документ1.Остатки")]
 right: []
thread 'lower::register_table_tests::every_computed_table_of_an_accounting_register_is_a_virtual_table' panicked at crates\sdbl-hir\src\lower\register_table_tests.rs:600:9:
assertion `left == right` failed
  left: []
 right: [("VirtualTableCallWithoutParameters", "РегистрБухгалтерии.Бухгалтерия1.ОборотыДтКт")]
thread 'lower::register_table_tests::common_attributes_are_columns_of_the_register_tables' panicked at crates\sdbl-hir\src\lower\register_table_tests.rs:548:9:
assertion `left == right` failed: РегистрНакопления.Остатки1.Остатки(&Период, )
  left: []
 right: ["НетТакогоПоля123"]
test result: FAILED. 10 passed; 15 failed; 0 ignored; 0 measured; 240 filtered out

Упали 15: все модели полей регистров накопления, сведений и бухгалтерии, отсутствующая таблица, классификация
(Документ.Документ1.Остатки и временная Остатки - ложное VirtualTableCallWithoutParameters, ОборотыДтКт /
ДвиженияССубконто не распознаны), общие реквизиты, поле через псевдоним присоединённой виртуальной таблицы и
an_ambiguous_output_is_reported_once_with_its_sources (на develop Счет в таблице остатков регистра бухгалтерии не
известен вовсе). Проходят 10 контрольных: молчание без плана счетов и без метаданных, регистр расчёта, таблица
изменений, аргумент системы компоновки, реквизит в Обороты, кадр параметров, ИТОГИ / УПОРЯДОЧИТЬ ПО и колонка
неизвестного типа - на develop эти места молчат, потому что полей не знают; тесты защищают от ложных находок,
которые точная модель иначе внесла бы.

ide-diagnostics на develop:

test handlers::unknown_field_in_query::tests::positive_unknown_field_on_register_slice ... FAILED
Expect:
----
UnknownFieldInQuery @ 3:31..3:44
  message: Поле "НетТакогоПоля" не найдено в таблице "РегистрСведений.РегистрСведений1.СрезПоследних" запроса
test result: FAILED. 22 passed; 1 failed; 0 ignored; 0 measured; 2178 filtered out

На ветке:

   Compiling sdbl-hir v0.3.2 (...\crates\sdbl-hir)
test result: ok. 25 passed; 0 failed; 0 ignored; 0 measured; 242 filtered out
   Compiling ide-diagnostics v0.3.2 (...\crates\ide-diagnostics)
test result: ok. 27 passed; 0 failed; 0 ignored; 0 measured; 2178 filtered out

Неоднозначность с пустым списком таблиц

Первая версия разрешения через выходные колонки пропускала колонку неизвестного типа и шла к источникам. Тесты
an_output_of_unknown_type_still_owns_its_name_in_order_by и an_ambiguous_output_is_reported_once_with_its_sources
до исправления (Scope::push_outputs_frame):

  left: [("Счет", [""])]
 right: []

  left: [("Счет", ["Конец", "Начало"]), ("Счет", [""])]
 right: [("Счет", ["Конец", "Начало"])]

Рабочие конфигурации

Release-сборки develop и ветки, analyze --no-extensions --no-externals --format jsonl на двух рабочих
конфигурациях с большим числом регистров: учётная (5407 модулей, 70 регистров накопления, регистр бухгалтерии,
~17 тыс. вхождений ВЫБРАТЬ) и торговая (10145 модулей, 168 регистров накопления, ~48 тыс. ВЫБРАТЬ). Сравнение
построчное (файл, код, строка, колонка, текст):

код учётная: ушло / новых торговая: ушло / новых
UnknownFieldInQuery 0 / 54 3 / 28
AmbiguousFieldInQuery 55 / 0 248 / 0
VirtualTableCallWithoutParameters 22 / 0 87 / 2
JoinWithVirtualTable 7 / 37 43 / 39
UnlimitedLengthStringUsageInQuery 0 / 0 1 / 0
InvalidVirtualTableParameters 0 / 0 0 / 0

Каждая новая находка сверена с XML регистра:

  • UnknownFieldInQuery, 82 новых, все истинные: поля, которых у регистра нет ни измерением, ни ресурсом, ни
    реквизитом: оборот ресурса, которого у регистра нет; измерение, которого нет, в таблице Остатки; ресурс,
    которого нет у регистра сведений, в срезе (в типовом коде регламентированных отчётов, в
    обработке свёртки базы и др.);
  • JoinWithVirtualTable, 76 новых: соединения с ОборотыДтКт, ДвиженияССубконто и
    ФактическийПериодДействия регистра расчёта - виртуальные таблицы, которые раньше не распознавались; находки по
    правилу верные;
  • VirtualTableCallWithoutParameters, 2 новых: РегистрБухгалтерии.<Имя>.ДвиженияССубконто без параметров - верные.

Ушедшие - ложные: AmbiguousFieldInQuery в УПОРЯДОЧИТЬ ПО / ИТОГИ по имени выходной колонки и в условии
параметров виртуальной таблицы; VirtualTableCallWithoutParameters / JoinWithVirtualTable на временных таблицах
Остатки / Обороты, табличных частях, хранимых Субконто / Изменения, вызовах с аргументом системы
компоновки; UnknownFieldInQuery «Узел не найден в <Объект>.Изменения»; UnlimitedLengthStringUsageInQuery на
УПОРЯДОЧИТЬ ПО по имени выходной колонки, которая уже приведена ВЫРАЗИТЬ(... КАК СТРОКА(1024)).

Новых InvalidVirtualTableParameters на этих двух конфигурациях нет; случаи покрыты тестами.

Проверка

  • cargo test -p bsl-metadata -p sdbl-hir -p ide-diagnostics -p ide --no-fail-fast: списки упавших на develop и на
    ветке совпадают (9 тестов, окружение Windows: a_self_qualified_call_*, graph::tests::method_graph_id_*,
    name_lookup::tests::the_address_phases_observe_a_cancelled_request и др.); прошедших 4126 на develop, 4160 на
    ветке.
  • cargo fmt --check по затронутым крейтам: чисто.
  • cargo clippy --all-targets -p bsl-metadata -p sdbl-hir -p ide-diagnostics -p ide: замечаний нет, как и на
    develop (сборочный скрипт ide-diagnostics сообщает 200 кодов вместо 199).

Поведение платформы

Запросы выполнены живьём на 8.3.17.1549 и 8.3.27.2214 (Новый Запрос(Текст).Выполнить()); имена регистров и полей
заменены на нейтральные, текст ошибок - дословно. На обеих версиях ответ одинаковый:

Запрос Ответ платформы
Т.Ресурс1Оборот из РегистрНакопления.Обороты1.Обороты (контроль) выполняется
Т.НетТакогоПоля123Оборот из .Обороты Поле не найдено "Т.НетТакогоПоля123Оборот"
.НетТаблицы() Таблица не найдена "РегистрНакопления.Обороты1.НетТаблицы"
.Обороты(, , , , ) - пять параметров Неверные параметры "РегистрНакопления.Обороты1.Обороты"
.Остатки у оборотного регистра Таблица не найдена "РегистрНакопления.Обороты1.Остатки"
Т.Субконто1 из РегистрБухгалтерии.Бухгалтерия1.Остатки (контроль) выполняется
Т.Субконто4 из РегистрБухгалтерии.Бухгалтерия1.Остатки (8.3.17; на 8.3.27 в проверочной базе нет регистра бухгалтерии) Поле не найдено "Т.Субконто4"

Одна из 82 новых находок на рабочей конфигурации проверена тем же способом: поле, которого нет среди ресурсов
оборотного регистра, платформа отвергает Поле не найдено, то есть находка указывает на запрос, который упадёт
при выполнении.

Вне этого PR

  • Таблицы регистра расчёта (БазаX, ДанныеГрафика, ФактическийПериодДействия, перерасчёты): поля не
    моделируются, таблица остаётся неполной; параметры не проверяются.
  • Поля таблицы изменений (Узел, НомерСообщения) и таблицы Субконто регистра бухгалтерии не моделируются - на
    них правило молчит.
  • Поля, зависящие от аргументов вызова (разбивка по периоду, Регистратор / НомерСтроки в оборотах), считаются
    существующими всегда; проверка «поле есть только при периодичности Регистратор» не делается.
  • Реквизиты регистра накопления в таблице Обороты оставлены provisional: типовой код их выбирает, а поведение
    платформы здесь не сверялось.
  • Тип поля Субконто<N> не выводится (неизвестен); проверяется только наличие имени.

🤖 Generated with Claude Code

flamber-dev and others added 2 commits October 14, 2026 12:54
…d on

The query field names of an accounting register are not its metadata names:
a correspondence register names a non-balance dimension or resource by its
sides (<Измерение>Дт, <Ресурс>Кт), only non-balance resources have the
correspondent-turnover fields (<Ресурс>КорОборот of a balance resource does
not exist), and the number of Субконто<N> fields comes from the chart of
accounts. None of that was parsed.

Parsed now, all as "absent means unknown" (None), never as false:
- Dimension / RegisterResource: Balance;
- Register: Correspondence and ChartOfAccounts (accounting registers only);
- MetadataObject: MaxExtDimensionCount of a chart of accounts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l tables

`query validate` answered "no findings" on a non-existent field of a register
virtual table, on any field of an accounting register (main table included),
on a non-existent virtual table and on an argument list the platform rejects
with «Неверные параметры». Only the top-level table was checked against
metadata, while the answer read like a full check.

Field models (all listed names a call may produce are offered; names that
exist only for some arguments or settings are provisional, so the
unknown-field rule stays silent on them and the ambiguity rule ignores them):
- accumulation Остатки / Обороты / ОстаткиИОбороты: dimensions plus the
  derived resource families (Остаток, Оборот, Приход, Расход, Начальный /
  КонечныйОстаток) and the per-period breakdown (ПериодМесяц, ...); register
  attributes stay provisional in Обороты because code shipped in a typical
  configuration selects one;
- information-register slices: Период, dimensions, resources, attributes;
- accounting registers, main table and Остатки / Обороты / ОборотыДтКт /
  ОстаткиИОбороты / ДвиженияССубконто, built from the balance /
  correspondence / chart-of-accounts metadata; without the chart the
  Субконто<N> family cannot be bounded and the table stays incomplete.
Common attributes follow the main table: a separator is a column of every
table, a plain common attribute a column of the tables that carry register
attributes, and an open common-attribute set leaves the table unchecked.

Table classification is now root-aware: a suffix is a virtual table only
under a register kind that owns it. The name-only test made a temporary
table or a tabular section called «Остатки» a balance table (false
VirtualTableCallWithoutParameters and JoinWithVirtualTable), never
recognised ОборотыДтКт / ДвиженияССубконто (the list carried misspelt
names), and treated the stored Субконто / Изменения tables as parameterised
calls. A third segment a register kind does not own is QueryToMissingMetadata
(not for calculation registers, whose base and recalculation tables are named
by the configuration).

New diagnostic InvalidVirtualTableParameters: more arguments than the table
has parameters (a condition split by a comma instead of И), or a condition
in a slot that takes a value (an account condition in the Субконто slot).
Positions are read from the commas of the call; a call that did not parse
cleanly (e.g. a template macro) is skipped.

Scope fixes the better typing exposed: a virtual table's parameter list is an
opaque frame (its condition speaks of the table's own fields only), and
ИТОГИ / УПОРЯДОЧИТЬ ПО resolve bare names against the select outputs first —
otherwise two joined balance tables made «Счет» falsely ambiguous. An output
owns its name whatever its type: an output of unknown type (a parameter) or an
output that is itself ambiguous must not send the name on to the sources,
which reported an ambiguity with an empty list of tables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@itrous

itrous commented Oct 14, 2026

Copy link
Copy Markdown
Owner

🤖 Начата автоматическая приёмка — этот pull request взят в работу ИИ-агентом: изменения переносятся на актуальную целевую ветку, проходят независимое ревью и соответствующие тесты, очевидные замечания правятся по ходу. Обновления появятся в этом треде; merge возможен только после завершения проверки.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants