v1.16.0
Добавлено
- Точная привязка рёбер графа вызовов к методу (
callee_key) + точный режимget_callers/find_call_hierarchy. Корневая неточность реверс-графа была в том, что рёбра хранились только по имени (callee_name) без привязки к конкретному методу — одноимённыеПриЗаписи/ОбработкаПроведенияиз сотен модулей при обходе склеивались в ложные звенья. Теперь каждое ребро при построении пытается разрешиться в стабильный текстовый ключ целиcallee_key = "<rel_path>::<casefold(метод)>"(схема builder v14, новая nullable-колонкаcalls.callee_key+ индекс).- Ключ основан на
rel_path(UNIQUE, переживает инкремент и пересоздание rowid изменённого модуля) — а не наmethods.id(rowid переназначается при перестройке → висячие ссылки). Один helper построения ключа общий для build-time и query-time → побайтовое совпадение. - Резолвятся только два доказуемо-безопасных tier'а: local (голый
B()→ метод того же модуля) и common_exported (A.B()→ экспортный метод общего модуляA). Object/manager-вызовы (Справочники.X.Метод,Контрагент.Метод) и платформенные/внешние методы намеренно не резолвятся (callee_key IS NULL) — это методы переменной/менеджера/платформы, автоматический резолв дал бы ложные привязки. Неоднозначность → тоже NULL. Нерезолвленные рёбра честно остаются на матче по имени (recall не теряется). get_callers(иfind_callers_context): если цель разрешается в один модуль — точный режим (матч поcallee_key, коллизии одноимённых методов из других модулей отсекаются) + нерезолвленные name-рёбра как fallback. В_metaдобавленыexact_rows/fallback_rows/exact_available— точные рёбра отделены от эвристических.find_call_hierarchy(name, …, module_hint='')— новый параметрmodule_hintдизамбигуирует КОРЕНЬ для одноимённых объектных методов (формы:rel_path|'Документ.X'/'Document.X'| голыйobject_name). На глубине обход распространяетrel_pathнайденного caller'а → точность сохраняется автоматически. Cycle protection переведён с visited-by-name на visited-by-target (в точных ветках), чтобы одноимённые caller'ы из разных модулей не схлопывались. Новая форма результата: узел{name, target_hint, target_key, meta, callers}+ top-level_meta(exact_available,root_exact,exact_targets/fallback_targets,exact_rows/fallback_rows,node_budget_exceeded/visited_cap).- Агрегаты резолва пишутся в
index_meta:calls_total/calls_resolved. - Help/strategy-слой обновлён в 4 местах (
_reg, рецепт «иерархия вызовов», disambiguation-правило, slim/full-упоминания), чтобыmodule_hintи поля доверия_metaдошли до агента.
- Ключ основан на
Исправлено
- Производительность
get_callers/find_call_hierarchy— выражение-индекс по имени метода вместо leading-wildcard скана. Матч вызывающих по имени использовалcallee_name = ? OR callee_name LIKE '%.имя'; ведущий%в LIKE не использует индекс → полный скан таблицыcalls(~3.7M строк) на каждый узел. Посколькуcallee_nameвсегда имеет ≤1 точку (регекс(\w+)\.(\w+)), суффикс после точки = имя метода → матч сведён к равенству по индексируемому выражению. Новый индексidx_calls_callee_short(в_SCHEMA_SQL, единый источник выражения_callee_short_expr); запрос строит общий helper_callee_match_clause(голое имя → суффикс-индекс; ввод с точкойМодуль.Метод→ полныйcallee_nameпоidx_calls_callee).BUILDER_VERSIONне менялся (индекс — чистая оптимизация, запрос корректен и без него); v13→v14 и так форсит rebuild, а уже собранные v14-базы получают индекс через безусловный self-heal_ensure_callee_short_indexпри любомupdate()(вкл. no-op). EXPLAIN-страж в тестах фиксирует использование индекса (защита от молчаливого дрейфа выражения / потериCOLLATE NOCASE). - Производительность
find_call_hierarchy— резолвер цели и проверка пустотыcalls. End-to-end замер на живой ERP (≈3.7M рёбер, 493K методов) показал, что headline-кейсfind_call_hierarchy('ОбработкаПроведения', depth=2)без hint оставался медленным из-за двух per-node издержек, не покрытых индексом по имени: (1)_resolve_target_keyделал полныйSCAN493K методов — запросmethods JOIN modules WHERE rel_path=? AND py_lower(name)=py_lower(?)не имел индекса на FK-колонкеmethods(module_id)(аpy_lower(col)убиваетidx_meth_name); (2)get_callersиIndexReader.has_callsначинали сSELECT COUNT(*) FROM calls(полный счёт ~3.7M ≈112мс) для простой проверки «таблица не пуста». Исправлено: новый индексidx_meth_module ON methods(module_id)(в_SCHEMA_SQL+ безусловный self-heal_ensure_meth_module_indexприupdate(), как уidx_calls_callee_short) → резолвер уходит соSCAN methodsна индексныйSEARCH (module_id=?); обе проверки пустоты заменены наSELECT 1 FROM calls LIMIT 1(O(1)).BUILDER_VERSIONне менялся. Перф-стражи в тестах: EXPLAIN резолвера используетidx_meth_module, а trace-страж запрещает нефильтрованныйCOUNT(*) FROM callsвget_callers/has_calls. - Защита
find_call_hierarchyот взрыва обхода на широком корне без hint. Корректный visited-by-target (одноимённые методы из разных модулей — разные узлы) на корне-«магните» вродеОбработкаПроведения(его зовут ~сотни документов, каждый своимОбработкаПроведения) разворачивался в сотни точных узлов. Добавлен backstop_HIERARCHY_VISITED_CAP=2000: BFS по уровням останавливается до следующего дорогого lookup'а, возвращает частичное (связное, shallow-first) дерево и честно помечает_meta.node_budget_exceeded=True+visited_cap. Семантика visited-by-target не изменена (однофамильцы по-прежнему не схлопываются). Недостижим для малых/средних деревьев. - Ложные рёбра графа вызовов из многострочных текстов запросов. Извлечение вызовов (
_extract_calls_from_body) переведено с построчного_strip_code_lineна многострочный сканер_scan_module. Функции языка запросов внутри многострочных строковых литералов (тексты запросов 1С с|-продолжением:ЕСТЬNULL(,ВЫРАЗИТЬ(,СУММА(,ЧИСЛО() больше не попадают в граф как ложные рёбра.""-эскейпы и//внутри строкового литерала тоже трактуются корректно._strip_code_lineсохранён (используется в FS-fallbackfind_callers_context).
Совместимость
- Схема
builder_version13 → 14. Апгрейд индекса со старее v14 выполняет разовый полный rebuild (новая колонкаcallee_keyсоздаётся и заполняется при пересборке). v13-индекс, открытый на ЧТЕНИЕ до фоновой пересборки, работает в name-based режиме без падения:get_callersотдаётexact_available=False, точный режим выключен (capability-check на наличие колонки). - Изменение семантики
get_callersдля НЕэкспортного метода сmodule_hint: старая эвристика «non-export → только свой файл» заменена точнымcallee_key; same-module local-вызов резолвится точно, кросс-модульная (нерезолвленная) ссылка честно отдаётся как fallback-ребро (recall сохранён, помечено в_meta).
Тесты
- Новый файл
tests/test_call_resolution.py: Фаза A (ложные query-рёбра,""///-эскейпы, абсолютная нумерация строк после многострочной строки), build-time ключи (common-exported/local/object-manager→NULL/платформа→NULL), точный режимget_callers(уникальный экспортный + отсечение коллизий + fallback),find_call_hierarchy(hint на корне, propagation на глубину, visited-by-target, форма результата), нормализацияmodule_hint(RU/EN/rel_path/bare), инкремент-устойчивость ключа при смене rowid цели, чтение v13-индекса без rebuild, миграция v13→14, help-слой. Перф-фикс: наличиеidx_calls_callee_shortпосле build (вкл. пустой репозиторий) и после no-opupdate()(self-heal вне delta-блока), EXPLAIN-страж использования индекса + MULTI-INDEX OR, recall-паритет суффикса для голого и dotted-ввода, инвариант ≤1 точки вcallee_name, контракт и срабатывание node-budget guard. Резолвер-индекс: наличиеidx_meth_moduleпосле build/пустого-репо/no-op-update(), EXPLAIN-страж использования индекса резолвером (черезIndexReader._conn— там есть UDFpy_lower), неизменность резолва ключа по всем формам hint; trace-страж против нефильтрованногоCOUNT(*) FROM calls+ поведение проверки пустоты. - Бамп
builder_version-ассертов до 14 в существующих тестах.
Полный список изменений: CHANGELOG.md