From 3bd9a561cd85ae7f864e12049bb29b92d25bba6e Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 22 Mar 2026 09:15:30 +0000 Subject: [PATCH 1/3] Initial commit with task details Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: https://github.com/netkeep80/BinDiffSynchronizer/issues/207 --- .gitkeep | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitkeep diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 0000000..f122930 --- /dev/null +++ b/.gitkeep @@ -0,0 +1 @@ +# .gitkeep file auto-generated at 2026-03-22T09:15:30.017Z for PR creation at branch issue-207-74dc2dfe1dcb for issue https://github.com/netkeep80/BinDiffSynchronizer/issues/207 \ No newline at end of file From 6841571416eb2120e1eb91e9198e4e5e052b6356 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 22 Mar 2026 09:29:59 +0000 Subject: [PATCH 2/3] =?UTF-8?q?perf:=20=D0=BE=D0=BF=D1=82=D0=B8=D0=BC?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20tag-=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=BE=D0=BA=20=D0=BD=D0=B0=20=D0=B3=D0=BE?= =?UTF-8?q?=D1=80=D1=8F=D1=87=D0=B8=D1=85=20=D0=BF=D1=83=D1=82=D1=8F=D1=85?= =?UTF-8?q?=20=E2=80=94=20=D1=81=D0=BE=D0=BA=D1=80=D0=B0=D1=89=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B8=D0=B7=D0=B1=D1=8B=D1=82=D0=BE=D1=87=D0=BD?= =?UTF-8?q?=D1=8B=D1=85=20pmm=5Fresolve=20(=D0=AD=D1=82=D0=B0=D0=BF=2010.3?= =?UTF-8?q?,=20Issue=20#207)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Оптимизированы горячие пути для сокращения избыточных вызовов pmm_resolve(): - is_number(): единственный tag() вместо трёх отдельных вызовов - is_null(): убрана избыточная проверка is_error() - deref(): единственный pmm_resolve за итерацию - pjson_traverse_subtree/postorder: единственный tag() вместо is_array()+is_object() - pjson_free_node_visitor: n->tag напрямую вместо повторного v.tag() - pjson_count_visitor: единственный pmm_resolve для тега и данных - _walk_path()/erase(): единственный tag() вместо is_object()+is_array() Добавлены 14 тестов. Все 690 тестов проходят. Co-Authored-By: Claude Opus 4.6 --- pjson_db_helpers.h | 31 ++-- pjson_db_pmm.h | 13 +- pjson_node.h | 16 +- plan.md | 28 ++-- readme.md | 3 +- tests/CMakeLists.txt | 1 + tests/test_pjson_tag_opt.cpp | 285 +++++++++++++++++++++++++++++++++++ 7 files changed, 342 insertions(+), 35 deletions(-) create mode 100644 tests/test_pjson_tag_opt.cpp diff --git a/pjson_db_helpers.h b/pjson_db_helpers.h index 61d65de..9c581ca 100644 --- a/pjson_db_helpers.h +++ b/pjson_db_helpers.h @@ -111,7 +111,9 @@ template inline void pjson_traverse_subtree( node_id id, Visi vis.visit( id, v ); - if ( v.is_array() ) + // Единственный вызов tag() вместо отдельных is_array() + is_object() (Этап 10.3). + node_tag t = v.tag(); + if ( t == node_tag::array ) { uintptr_t sz = v.size(); for ( uintptr_t i = 0; i < sz; ++i ) @@ -121,7 +123,7 @@ template inline void pjson_traverse_subtree( node_id id, Visi pjson_traverse_subtree( elem.id, vis ); } } - else if ( v.is_object() ) + else if ( t == node_tag::object ) { uintptr_t sz = v.size(); for ( uintptr_t i = 0; i < sz; ++i ) @@ -144,7 +146,9 @@ template inline void pjson_traverse_subtree_postorder( node_i if ( !v.valid() ) return; - if ( v.is_array() ) + // Единственный вызов tag() вместо отдельных is_array() + is_object() (Этап 10.3). + node_tag t = v.tag(); + if ( t == node_tag::array ) { uintptr_t sz = v.size(); for ( uintptr_t i = 0; i < sz; ++i ) @@ -154,7 +158,7 @@ template inline void pjson_traverse_subtree_postorder( node_i pjson_traverse_subtree_postorder( elem.id, vis ); } } - else if ( v.is_object() ) + else if ( t == node_tag::object ) { uintptr_t sz = v.size(); for ( uintptr_t i = 0; i < sz; ++i ) @@ -177,10 +181,11 @@ template inline void pjson_traverse_subtree_postorder( node_i /// были освобождены раньше родителей. struct pjson_free_node_visitor { - void visit( node_id id, const node_view& v ) + void visit( node_id id, const node_view& /* v */ ) { node* n = pmm_resolve( id ); - switch ( v.tag() ) + // Используем n->tag напрямую вместо v.tag(), чтобы избежать повторного pmm_resolve (Этап 10.3). + switch ( n != nullptr ? n->tag : node_tag::null ) { case node_tag::array: if ( n != nullptr ) @@ -225,10 +230,14 @@ struct pjson_count_visitor uint64_t& object_cnt; uint64_t& binary_bytes; - void visit( node_id id, const node_view& v ) + void visit( node_id id, const node_view& /* v */ ) { ++node_cnt; - switch ( v.tag() ) + // Единственный pmm_resolve: читаем tag напрямую из узла (Этап 10.3). + const node* n = pmm_resolve( id ); + if ( n == nullptr ) + return; + switch ( n->tag ) { case node_tag::array: ++array_cnt; @@ -240,12 +249,8 @@ struct pjson_count_visitor ++ref_cnt; break; case node_tag::binary: - { - const node* n = pmm_resolve( id ); - if ( n != nullptr ) - binary_bytes += static_cast( n->binary_val.size() ); + binary_bytes += static_cast( n->binary_val.size() ); break; - } default: break; } diff --git a/pjson_db_pmm.h b/pjson_db_pmm.h index 5372228..e80f613 100644 --- a/pjson_db_pmm.h +++ b/pjson_db_pmm.h @@ -288,7 +288,9 @@ class pjson_db_pmm node_view parent{ parent_id }; - if ( parent.is_object() ) + // Единственный вызов tag() вместо отдельных is_object() + is_array() (Этап 10.3). + node_tag parent_tag = parent.tag(); + if ( parent_tag == node_tag::object ) { node_view child = parent.at( last_seg.c_str() ); if ( !child.valid() ) @@ -299,7 +301,7 @@ class pjson_db_pmm _update_metrics_after_mutation(); return ok; } - else if ( parent.is_array() ) + else if ( parent_tag == node_tag::array ) { char* end_ptr = nullptr; uintptr_t idx = static_cast( std::strtoull( last_seg.c_str(), &end_ptr, 10 ) ); @@ -646,8 +648,11 @@ class pjson_db_pmm [[maybe_unused]] bool is_last = ( *p == '\0' ); + // Единственный вызов tag() вместо отдельных is_object() + is_array() (Этап 10.3). + node_tag cur_tag = cur_v.tag(); + // --- навигация по object --- - if ( cur_v.is_object() ) + if ( cur_tag == node_tag::object ) { node_view child = cur_v.at( seg.c_str() ); if ( child.valid() ) @@ -674,7 +679,7 @@ class pjson_db_pmm } } // --- навигация по array --- - else if ( cur_v.is_array() ) + else if ( cur_tag == node_tag::array ) { char* end_ptr = nullptr; uintptr_t idx = static_cast( std::strtoull( seg.c_str(), &end_ptr, 10 ) ); diff --git a/pjson_node.h b/pjson_node.h index 6a99820..6dfc5a3 100644 --- a/pjson_node.h +++ b/pjson_node.h @@ -305,12 +305,18 @@ struct node_view /// Проверить, является ли узел JSON null-значением. /// Возвращает true только для id==0 или узлов с tag==null (но не для ошибочных view). - bool is_null() const { return !is_error() && tag() == node_tag::null; } + bool is_null() const { return tag() == node_tag::null; } bool is_boolean() const { return tag() == node_tag::boolean; } bool is_integer() const { return tag() == node_tag::integer; } bool is_uinteger() const { return tag() == node_tag::uinteger; } bool is_real() const { return tag() == node_tag::real; } - bool is_number() const { return is_integer() || is_uinteger() || is_real(); } + /// Проверить, является ли узел числовым (integer, uinteger или real). + /// Выполняет единственный pmm_resolve вместо трёх отдельных вызовов tag(). + bool is_number() const + { + node_tag t = tag(); + return t == node_tag::integer || t == node_tag::uinteger || t == node_tag::real; + } bool is_string() const { return tag() == node_tag::string; } bool is_binary() const { return tag() == node_tag::binary; } bool is_array() const { return tag() == node_tag::array; } @@ -517,14 +523,16 @@ struct node_view /// Если recursive == true, разыменовывает цепочку ref-узлов. /// Защита от зависания: ограничение глубиной max_depth. /// Если текущий узел не ref — возвращает себя. + /// Оптимизировано: единственный pmm_resolve за итерацию (вместо отдельных is_ref + ref_target). node_view deref( bool recursive = true, uintptr_t max_depth = PJSON_MAX_REF_DEPTH ) const { node_view cur = *this; for ( uintptr_t depth = 0; depth < max_depth; depth++ ) { - if ( !cur.is_ref() ) + const node* n = cur._resolve(); + if ( n == nullptr || n->tag != node_tag::ref ) return cur; - node_id target = cur.ref_target(); + node_id target = n->ref_val.target; if ( target == 0 ) return node_view{}; // не разрешён node_view next{ target }; diff --git a/plan.md b/plan.md index 85d16e1..5b155b7 100644 --- a/plan.md +++ b/plan.md @@ -21,7 +21,7 @@ | 7. Унификация итераторов | CRTP-база pjson_iterator_base + шаблонный pjson_range; ~22 строки удалено | ✅ | **Итого удалено:** ~5 файлов (~1900 строк), ~381 строка дублирования. -**Тесты:** 676 тестов, ~360 000 assertion. +**Тесты:** 690 тестов, ~360 000 assertion. --- @@ -91,19 +91,20 @@ --- -### Проблема 10: node_view::tag() вызывает _resolve() при каждой проверке is_*() +### ~~Проблема 10: node_view::tag() вызывает _resolve() при каждой проверке is_*()~~ ✅ -**Файл:** `pjson_node.h`, строки 283–308 +**Решено в Этапе 10.3:** Оптимизированы горячие пути для сокращения избыточных вызовов `pmm_resolve()`: -Каждый вызов `is_string()`, `is_array()`, `is_number()` и т.д. вызывает `tag()`, который выполняет `pmm_resolve(id)`. При цепочке проверок (`is_null() || is_boolean() || is_integer() || ...`) один и тот же узел разрешается многократно. +1. **`is_number()`** — переписан с единственным вызовом `tag()` вместо трёх (через `is_integer()`, `is_uinteger()`, `is_real()`). +2. **`is_null()`** — убрана избыточная проверка `is_error()` (уже обрабатывается внутри `tag()`). +3. **`deref()`** — единственный `pmm_resolve` за итерацию вместо отдельных `is_ref()` + `ref_target()`. +4. **`pjson_traverse_subtree` / `pjson_traverse_subtree_postorder`** — единственный `tag()` вместо цепочки `is_array()` + `is_object()`. +5. **`pjson_free_node_visitor`** — используется `n->tag` напрямую вместо повторного `v.tag()`. +6. **`pjson_count_visitor`** — единственный `pmm_resolve` для получения тега и данных. +7. **`_walk_path()`** — единственный `tag()` вместо цепочки `is_object()` + `is_array()`. +8. **`erase()`** — единственный `tag()` вместо цепочки `is_object()` + `is_array()`. -Метод `is_number()` вызывает `tag()` трижды: - -```cpp -bool is_number() const { return is_integer() || is_uinteger() || is_real(); } -``` - -**Решение (низкий приоритет):** Кэширование тега в node_view не подходит (node_view — легковесный accessor). Можно рассмотреть метод `tag_checked()`, который одновременно проверяет тег и возвращает указатель, для горячих путей. +Добавлены 14 тестов. --- @@ -182,7 +183,7 @@ pvector был бы предпочтительнее **только** при ч |---|----------|------|-----------|---------| | ~~3~~ | ~~Глобальное состояние PMM (Этап A)~~ | ~~pam_pmm.h~~ | ~~Высокая~~ | ✅ | | ~~7~~ | ~~Нет escaping '/' в путях~~ | ~~pjson_db_pmm.h~~ | ~~Средняя~~ | ✅ | -| 10 | Многократный resolve в is_*() | pjson_node.h | Средняя | Производительность | +| ~~10~~ | ~~Многократный resolve в is_*()~~ | ~~pjson_node.h~~ | ~~Средняя~~ | ✅ | | 11 | const-корректность _walk_path | pjson_db_pmm.h | Средняя | Корректность | --- @@ -207,7 +208,7 @@ pvector был бы предпочтительнее **только** при ч Этап 10: Приоритет 3 — архитектурные улучшения 10.1 ✅ Инкапсуляция глобального состояния pam_pmm (Этап A: структура pam_pmm_state + синглтон) 10.2 ✅ Поддержка RFC 6901 (JSON Pointer) для путей - 10.3 Оптимизация tag-проверок на горячих путях + 10.3 ✅ Оптимизация tag-проверок на горячих путях 10.4 const-корректность с явной передачей состояния ``` @@ -217,6 +218,7 @@ pvector был бы предпочтительнее **только** при ч | Дата | Изменение | |------|-----------| +| 2026-03-22 | Этап 10.3: оптимизация tag-проверок на горячих путях — сокращение избыточных pmm_resolve (Issue #207) | | 2026-03-22 | Этап 10.2: поддержка RFC 6901 (JSON Pointer) для путей — escaping ~/slash в ключах (Issue #206) | | 2026-03-22 | Этап 10.1: инкапсуляция глобального состояния pam_pmm в структуру pam_pmm_state (Issue #205) | | 2026-03-22 | Этап 9.4: parse_object() в один проход без двойного парсинга (Issue #192) | diff --git a/readme.md b/readme.md index 611ac37..74c422c 100644 --- a/readme.md +++ b/readme.md @@ -139,7 +139,7 @@ int main() { | `pjson_db_pmm.h` | D | Менеджер персистной JSON-БД: path-адресация, `put`/`get`/`erase`, `$ref`, метрики, поиск, клонирование | | `deps/pmm/pmm.h` | A | [PersistMemoryManager](https://github.com/netkeep80/PersistMemoryManager) — бэкенд ПАП | | `main.cpp` | — | Демонстрационная программа | -| `tests/` | — | Тесты на Catch2 (676 тестов, ~360 000 assertion) | +| `tests/` | — | Тесты на Catch2 (690 тестов, ~360 000 assertion) | | `CMakeLists.txt` | — | Система сборки (CMake 3.16+, C++20) | --- @@ -454,6 +454,7 @@ db.put("/copy/name", "Bob"); - **Глобальное состояние PMM** — в одном процессе может быть открыта только одна БД (см. [plan.md](plan.md), Проблема 3); состояние инкапсулировано в `pam_pmm_state`, передача как параметра — в будущих версиях - ~~**Нет escaping `/` в путях**~~ — **Исправлено** в Этапе 10.2: поддержка RFC 6901 (JSON Pointer) — `~1` для `/`, `~0` для `~` в сегментах путей - ~~**Утечка временных узлов метрик**~~ — **Исправлено** в Этапе 8.4: один pre-allocated узел переиспользуется для всех вызовов метрик +- ~~**Многократный resolve в is_*() проверках**~~ — **Исправлено** в Этапе 10.3: `is_number()`, `deref()`, traversal и walk_path оптимизированы для единственного `pmm_resolve` вместо повторных вызовов - **Не потокобезопасно** — CacheManagerConfig (по умолчанию) использует NoLock; для многопоточности нужен PersistentDataConfig - **Строки не освобождаются** — словарь `pstringview_pmm` только растёт diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 46706d3..0d4cc28 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -39,6 +39,7 @@ set(TEST_SOURCES test_compat_save_load.cpp test_pjson_ref_stability.cpp test_pjson_rfc6901.cpp + test_pjson_tag_opt.cpp ) add_executable(tests ${TEST_SOURCES}) diff --git a/tests/test_pjson_tag_opt.cpp b/tests/test_pjson_tag_opt.cpp new file mode 100644 index 0000000..234a20f --- /dev/null +++ b/tests/test_pjson_tag_opt.cpp @@ -0,0 +1,285 @@ +#include + +#include +#include + +#include "pjson_db_pmm.h" + +using namespace pjson; + +// Вспомогательная функция: сбросить PMM перед каждым тестом. +namespace +{ +void reset_pam() +{ + pstringview_manager::reset(); + pam_pmm_reset(); +} +} // anonymous namespace + +// ============================================================================= +// Этап 10.3: Оптимизация tag-проверок — тесты корректности +// ============================================================================= + +// --------------------------------------------------------------------------- +// is_number: единственный resolve вместо трёх +// --------------------------------------------------------------------------- +TEST_CASE( "tag_opt: is_number returns true for integer", "[pjson_node][tag_opt]" ) +{ + reset_pam(); + fptr fn; + fn.New(); + node_set_int( fn.addr(), 42 ); + node_view v{ fn.addr() }; + REQUIRE( v.is_number() ); + REQUIRE( v.is_integer() ); + REQUIRE_FALSE( v.is_uinteger() ); + REQUIRE_FALSE( v.is_real() ); + fn.Delete(); +} + +TEST_CASE( "tag_opt: is_number returns true for uinteger", "[pjson_node][tag_opt]" ) +{ + reset_pam(); + fptr fn; + fn.New(); + node_set_uint( fn.addr(), 99u ); + node_view v{ fn.addr() }; + REQUIRE( v.is_number() ); + REQUIRE_FALSE( v.is_integer() ); + REQUIRE( v.is_uinteger() ); + REQUIRE_FALSE( v.is_real() ); + fn.Delete(); +} + +TEST_CASE( "tag_opt: is_number returns true for real", "[pjson_node][tag_opt]" ) +{ + reset_pam(); + fptr fn; + fn.New(); + node_set_real( fn.addr(), 3.14 ); + node_view v{ fn.addr() }; + REQUIRE( v.is_number() ); + REQUIRE_FALSE( v.is_integer() ); + REQUIRE_FALSE( v.is_uinteger() ); + REQUIRE( v.is_real() ); + fn.Delete(); +} + +TEST_CASE( "tag_opt: is_number returns false for non-numeric types", "[pjson_node][tag_opt]" ) +{ + reset_pam(); + + // null + { + node_view v{}; + REQUIRE_FALSE( v.is_number() ); + } + + // boolean + { + fptr fn; + fn.New(); + node_set_bool( fn.addr(), true ); + REQUIRE_FALSE( node_view{ fn.addr() }.is_number() ); + fn.Delete(); + } + + // string + { + reset_pam(); + fptr fn; + fn.New(); + node_set_string( fn.addr(), "hello" ); + REQUIRE_FALSE( node_view{ fn.addr() }.is_number() ); + fn.Delete(); + } + + // array + { + reset_pam(); + fptr fn; + fn.New(); + node_set_array( fn.addr() ); + REQUIRE_FALSE( node_view{ fn.addr() }.is_number() ); + fn.Delete(); + } + + // object + { + reset_pam(); + fptr fn; + fn.New(); + node_set_object( fn.addr() ); + REQUIRE_FALSE( node_view{ fn.addr() }.is_number() ); + fn.Delete(); + } +} + +// --------------------------------------------------------------------------- +// is_null: корректное поведение для null, error и валидных узлов +// --------------------------------------------------------------------------- +TEST_CASE( "tag_opt: is_null returns true for default node_view", "[pjson_node][tag_opt]" ) +{ + node_view v{}; + REQUIRE( v.is_null() ); + REQUIRE_FALSE( v.is_error() ); +} + +TEST_CASE( "tag_opt: is_null returns false for error node_view", "[pjson_node][tag_opt]" ) +{ + node_view v = node_view_error( node_error::not_found ); + REQUIRE_FALSE( v.is_null() ); + REQUIRE( v.is_error() ); +} + +TEST_CASE( "tag_opt: is_null returns true for freshly allocated node (tag==null by default)", "[pjson_node][tag_opt]" ) +{ + reset_pam(); + fptr fn; + fn.New(); + // Свежевыделенный узел имеет tag==null по умолчанию. + node_view v{ fn.addr() }; + REQUIRE( v.is_null() ); + REQUIRE_FALSE( v.is_error() ); + fn.Delete(); +} + +TEST_CASE( "tag_opt: is_null returns false for non-null node", "[pjson_node][tag_opt]" ) +{ + reset_pam(); + fptr fn; + fn.New(); + node_set_int( fn.addr(), 7 ); + REQUIRE_FALSE( node_view{ fn.addr() }.is_null() ); + fn.Delete(); +} + +// --------------------------------------------------------------------------- +// deref: оптимизированный единственный resolve за итерацию +// --------------------------------------------------------------------------- +TEST_CASE( "tag_opt: deref returns self for non-ref node", "[pjson_node][tag_opt]" ) +{ + reset_pam(); + fptr fn; + fn.New(); + node_set_int( fn.addr(), 100 ); + node_view v{ fn.addr() }; + node_view d = v.deref(); + REQUIRE( d.id == v.id ); + REQUIRE( d.as_int() == 100 ); + fn.Delete(); +} + +TEST_CASE( "tag_opt: deref resolves ref chain", "[pjson_node][tag_opt]" ) +{ + reset_pam(); + pjson_db_pmm db; + + // Создаём целевой узел и ref на него. + db.put( "/target", static_cast( 42 ) ); + db.put_ref( "/link", "/target" ); + db.resolve_all_refs(); + + node_view link = db.get( "/link", /*deref_refs=*/false ); + REQUIRE( link.is_ref() ); + REQUIRE( link.ref_target() != 0u ); + + node_view resolved = link.deref(); + REQUIRE( resolved.valid() ); + REQUIRE( resolved.as_int() == 42 ); +} + +TEST_CASE( "tag_opt: deref detects cycle", "[pjson_node][tag_opt]" ) +{ + reset_pam(); + + // Создаём ref, который ссылается на самого себя. + fptr fn; + fn.New(); + node_set_ref( fn.addr(), "/self" ); + node_set_ref_target( fn.addr(), fn.addr() ); + + node_view v{ fn.addr() }; + node_view d = v.deref(); + // Цикл: deref возвращает null. + REQUIRE_FALSE( d.valid() ); + + fn.Delete(); +} + +// --------------------------------------------------------------------------- +// Интеграция: erase корректно работает с оптимизированной проверкой тегов +// --------------------------------------------------------------------------- +TEST_CASE( "tag_opt: erase works for object and array after tag optimization", "[pjson_db][tag_opt]" ) +{ + reset_pam(); + pjson_db_pmm db; + + // Объект: erase ключа. + db.put( "/obj/a", 1 ); + db.put( "/obj/b", 2 ); + REQUIRE( db.exists( "/obj/a" ) ); + REQUIRE( db.erase( "/obj/a" ) ); + REQUIRE_FALSE( db.exists( "/obj/a" ) ); + REQUIRE( db.exists( "/obj/b" ) ); + + // Массив: erase элемента. + db.put( "/arr/0", 10 ); + db.put( "/arr/1", 20 ); + REQUIRE( db.erase( "/arr/0" ) ); + // После удаления элемента 0, элемент 1 сдвигается. + REQUIRE( db.get( "/arr/0" ).as_int() == 20 ); +} + +// --------------------------------------------------------------------------- +// Интеграция: walk_path корректно работает с оптимизированной проверкой тегов +// --------------------------------------------------------------------------- +TEST_CASE( "tag_opt: walk_path navigates objects and arrays correctly", "[pjson_db][tag_opt]" ) +{ + reset_pam(); + pjson_db_pmm db; + + // Глубокий путь через объект и массив. + db.put( "/a/b/0/c", "deep" ); + + node_view v = db.get( "/a/b/0/c" ); + REQUIRE( v.valid() ); + REQUIRE( v.is_string() ); + REQUIRE( v.as_string() == "deep" ); + + // Проверяем промежуточные типы. + REQUIRE( db.get( "/a" ).is_object() ); + REQUIRE( db.get( "/a/b" ).is_array() ); + REQUIRE( db.get( "/a/b/0" ).is_object() ); +} + +// --------------------------------------------------------------------------- +// Интеграция: traversal корректно обходит дерево с оптимизированными тегами +// --------------------------------------------------------------------------- +TEST_CASE( "tag_opt: traversal counts nodes correctly after tag optimization", "[pjson_db][tag_opt]" ) +{ + reset_pam(); + pjson_db_pmm db; + + // Создаём дерево: объект с массивом и вложенными элементами. + db.put( "/root/name", "test" ); + db.put( "/root/items/0", int64_t( 1 ) ); + db.put( "/root/items/1", int64_t( 2 ) ); + db.put( "/root/items/2", int64_t( 3 ) ); + + // Проверяем, что node_count_total > 0 (узлы были подсчитаны через оптимизированный traversal). + node_view nc = db.get( "/$metrics/node_count_total" ); + REQUIRE( nc.valid() ); + REQUIRE( nc.as_uint() > 0u ); + + // Проверяем array_count (items — массив). + node_view ac = db.get( "/$metrics/array_count" ); + REQUIRE( ac.valid() ); + REQUIRE( ac.as_uint() >= 1u ); + + // Проверяем object_count (root — объект). + node_view oc = db.get( "/$metrics/object_count" ); + REQUIRE( oc.valid() ); + REQUIRE( oc.as_uint() >= 1u ); +} From c716de3feb6a2fe3e71804570da6d6cc4a89c587 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 22 Mar 2026 09:38:22 +0000 Subject: [PATCH 3/3] Revert "Initial commit with task details" This reverts commit 3bd9a561cd85ae7f864e12049bb29b92d25bba6e. --- .gitkeep | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .gitkeep diff --git a/.gitkeep b/.gitkeep deleted file mode 100644 index f122930..0000000 --- a/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# .gitkeep file auto-generated at 2026-03-22T09:15:30.017Z for PR creation at branch issue-207-74dc2dfe1dcb for issue https://github.com/netkeep80/BinDiffSynchronizer/issues/207 \ No newline at end of file