Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 96 additions & 25 deletions pjson_db_pmm.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ class pjson_db_pmm
return _get_metrics( path );

node_error err = node_error::none;
node_id cur = _walk_path<false>( path, deref_refs, &err );
node_id cur = _walk_path_read( path, deref_refs, &err );
if ( cur == 0 )
return node_view_error( err );

Expand Down Expand Up @@ -586,17 +586,16 @@ class pjson_db_pmm
}

// -----------------------------------------------------------------------
// Общий обход пути: _walk_path
// Обход пути: _walk_path_read / _walk_path_create (Этап 10.4)
// -----------------------------------------------------------------------

/// Обход пути по сегментам с двумя режимами:
/// CreateMode=false (read) — возвращает 0 при отсутствии узла;
/// CreateMode=true (create) — создаёт промежуточные узлы.
/// Обход пути по сегментам (read-only).
/// Возвращает 0 при отсутствии узла, не создаёт промежуточных узлов.
/// @param path абсолютный путь вида /a/b/0/c
/// @param deref_refs разыменовывать ли $ref-узлы при обходе
/// @param out_err (только read) указатель на код ошибки (может быть nullptr)
/// @param out_err указатель на код ошибки (может быть nullptr)
/// @return node_id целевого узла или 0 при ошибке
template <bool CreateMode> node_id _walk_path( const char* path, bool deref_refs, node_error* out_err ) const
node_id _walk_path_read( const char* path, bool deref_refs, node_error* out_err ) const
{
auto fail = [&]( node_error e ) -> node_id
{
Expand Down Expand Up @@ -646,7 +645,91 @@ class pjson_db_pmm

node_view cur_v{ cur };

[[maybe_unused]] bool is_last = ( *p == '\0' );
// Единственный вызов tag() вместо отдельных is_object() + is_array() (Этап 10.3).
node_tag cur_tag = cur_v.tag();

// --- навигация по object ---
if ( cur_tag == node_tag::object )
{
node_view child = cur_v.at( seg.c_str() );
if ( child.valid() )
cur = child.id;
else
return fail( node_error::not_found );
}
// --- навигация по array ---
else if ( cur_tag == node_tag::array )
{
char* end_ptr = nullptr;
uintptr_t idx = static_cast<uintptr_t>( std::strtoull( seg.c_str(), &end_ptr, 10 ) );
if ( end_ptr == seg.c_str() || *end_ptr != '\0' )
return fail( node_error::wrong_type );

node_view elem = cur_v.at( idx );
if ( elem.valid() )
cur = elem.id;
else
return fail( node_error::index_out_of_range );
}
// --- ни object, ни array ---
else
{
return fail( node_error::wrong_type );
}
}

return cur;
}

/// Обход пути по сегментам (create).
/// Создаёт промежуточные узлы при их отсутствии.
/// @param path абсолютный путь вида /a/b/0/c
/// @param deref_refs разыменовывать ли $ref-узлы при обходе
/// @return node_id целевого узла или 0 при ошибке
node_id _walk_path_create( const char* path, bool deref_refs )
{
if ( path == nullptr )
return 0;

node_id cur = _find_root();
if ( cur == 0 )
return 0;

const char* p = path;
if ( *p == '/' )
++p;

while ( *p != '\0' )
{
// --- разбор сегмента ---
const char* seg_start = p;
while ( *p != '\0' && *p != '/' )
++p;
uintptr_t seg_len = static_cast<uintptr_t>( p - seg_start );
if ( *p == '/' )
++p;

if ( seg_len == 0 )
continue;

std::string seg = pjson_decode_rfc6901_segment( seg_start, seg_len );

// --- разыменование $ref ---
if ( deref_refs )
{
node_view cur_v{ cur };
if ( cur_v.is_ref() )
{
node_view resolved = cur_v.deref( true, 32 );
if ( !resolved.valid() )
return 0;
cur = resolved.id;
}
}

node_view cur_v{ cur };

bool is_last = ( *p == '\0' );

// Единственный вызов tag() вместо отдельных is_object() + is_array() (Этап 10.3).
node_tag cur_tag = cur_v.tag();
Expand All @@ -659,7 +742,7 @@ class pjson_db_pmm
{
cur = child.id;
}
else if constexpr ( CreateMode )
else
{
node_id slot = node_object_insert( cur, seg.c_str() );
if ( slot == 0 )
Expand All @@ -673,25 +756,21 @@ class pjson_db_pmm
}
cur = slot;
}
else
{
return fail( node_error::not_found );
}
}
// --- навигация по array ---
else if ( cur_tag == node_tag::array )
{
char* end_ptr = nullptr;
uintptr_t idx = static_cast<uintptr_t>( std::strtoull( seg.c_str(), &end_ptr, 10 ) );
if ( end_ptr == seg.c_str() || *end_ptr != '\0' )
return fail( node_error::wrong_type );
return 0;

node_view elem = cur_v.at( idx );
if ( elem.valid() )
{
cur = elem.id;
}
else if constexpr ( CreateMode )
else
{
uintptr_t cur_size = cur_v.size();
for ( uintptr_t i = cur_size; i <= idx; ++i )
Expand All @@ -710,22 +789,14 @@ class pjson_db_pmm
}
}
}
else
{
return fail( node_error::index_out_of_range );
}
}
// --- ни object, ни array ---
else if constexpr ( CreateMode )
else
{
if ( is_last )
return cur;
return 0;
}
else
{
return fail( node_error::wrong_type );
}
}

return cur;
Expand Down Expand Up @@ -943,7 +1014,7 @@ class pjson_db_pmm
return node_view{ tmp_off };
}

node_id _ensure_path( const char* path ) { return _walk_path<true>( path, true, nullptr ); }
node_id _ensure_path( const char* path ) { return _walk_path_create( path, true ); }

// -----------------------------------------------------------------------
// Вспомогательные методы: удаление узлов
Expand Down
17 changes: 9 additions & 8 deletions plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
| 7. Унификация итераторов | CRTP-база pjson_iterator_base + шаблонный pjson_range; ~22 строки удалено | ✅ |

**Итого удалено:** ~5 файлов (~1900 строк), ~381 строка дублирования.
**Тесты:** 690 тестов, ~360 000 assertion.
**Тесты:** 700 тестов, ~360 000 assertion.

---

Expand Down Expand Up @@ -108,13 +108,13 @@

---

### Проблема 11: Отсутствие const-корректности в _walk_path
### ~~Проблема 11: Отсутствие const-корректности в _walk_path~~ ✅

**Файл:** `pjson_db_pmm.h`, строка 593
**Решено в Этапе 10.4:** Шаблонный `_walk_path<bool CreateMode>() const` разделён на два метода с корректными квалификаторами:
- `_walk_path_read()` — `const`, только чтение, возвращает 0 при отсутствии узла;
- `_walk_path_create()` — без `const`, создаёт промежуточные узлы.

Метод `_walk_path<bool>()` объявлен `const`, но в режиме `CreateMode=true` модифицирует дерево узлов. Это возможно только потому, что PMM — глобальное состояние, и модификация происходит через глобальные функции, а не через членов класса.

**Связь:** Это следствие Проблемы 3 (глобальное состояние).
Устранена ложная `const`-квалификация метода, который в режиме `CreateMode=true` модифицировал дерево узлов через глобальное состояние PMM. Добавлены 10 тестов.

---

Expand Down Expand Up @@ -184,7 +184,7 @@ pvector был бы предпочтительнее **только** при ч
| ~~3~~ | ~~Глобальное состояние PMM (Этап A)~~ | ~~pam_pmm.h~~ | ~~Высокая~~ | ✅ |
| ~~7~~ | ~~Нет escaping '/' в путях~~ | ~~pjson_db_pmm.h~~ | ~~Средняя~~ | ✅ |
| ~~10~~ | ~~Многократный resolve в is_*()~~ | ~~pjson_node.h~~ | ~~Средняя~~ | ✅ |
| 11 | const-корректность _walk_path | pjson_db_pmm.h | Средняя | Корректность |
| ~~11~~ | ~~const-корректность _walk_path~~ | ~~pjson_db_pmm.h~~ | ~~Средняя~~ | |

---

Expand All @@ -209,7 +209,7 @@ pvector был бы предпочтительнее **только** при ч
10.1 ✅ Инкапсуляция глобального состояния pam_pmm (Этап A: структура pam_pmm_state + синглтон)
10.2 ✅ Поддержка RFC 6901 (JSON Pointer) для путей
10.3 ✅ Оптимизация tag-проверок на горячих путях
10.4 const-корректность с явной передачей состояния
10.4 const-корректность _walk_path: разделение на _walk_path_read (const) и _walk_path_create
```

---
Expand All @@ -218,6 +218,7 @@ pvector был бы предпочтительнее **только** при ч

| Дата | Изменение |
|------|-----------|
| 2026-03-22 | Этап 10.4: const-корректность _walk_path — разделение на _walk_path_read (const) и _walk_path_create (Issue #208) |
| 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) |
Expand Down
3 changes: 2 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 (690 тестов, ~360 000 assertion) |
| `tests/` | — | Тесты на Catch2 (700 тестов, ~360 000 assertion) |
| `CMakeLists.txt` | — | Система сборки (CMake 3.16+, C++20) |

---
Expand Down Expand Up @@ -455,6 +455,7 @@ db.put("/copy/name", "Bob");
- ~~**Нет 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` вместо повторных вызовов
- ~~**const-некорректность _walk_path**~~ — **Исправлено** в Этапе 10.4: шаблонный `_walk_path<bool>() const` разделён на `_walk_path_read() const` (только чтение) и `_walk_path_create()` (мутирующий)
- **Не потокобезопасно** — CacheManagerConfig (по умолчанию) использует NoLock; для многопоточности нужен PersistentDataConfig
- **Строки не освобождаются** — словарь `pstringview_pmm` только растёт

Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ set(TEST_SOURCES
test_pjson_ref_stability.cpp
test_pjson_rfc6901.cpp
test_pjson_tag_opt.cpp
test_pjson_const_correctness.cpp
)

add_executable(tests ${TEST_SOURCES})
Expand Down
Loading
Loading