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
43 changes: 40 additions & 3 deletions pjson_db_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,44 @@
// Вспомогательные функции: путевая адресация
// ===========================================================================

/// Декодировать сегмент пути по RFC 6901 (JSON Pointer):
/// ~1 → /
/// ~0 → ~
/// Порядок декодирования важен: сначала ~1, затем ~0.
inline std::string pjson_decode_rfc6901_segment( const char* data, uintptr_t len )
{
std::string result;
result.reserve( len );
for ( uintptr_t i = 0; i < len; ++i )
{
if ( data[i] == '~' && i + 1 < len )
{
if ( data[i + 1] == '1' )
{
result += '/';
++i;
continue;
}
else if ( data[i + 1] == '0' )
{
result += '~';
++i;
continue;
}
}
result += data[i];
}
return result;
}

/// Декодировать сегмент пути по RFC 6901 (JSON Pointer) из std::string.
inline std::string pjson_decode_rfc6901_segment( const std::string& seg )
{
return pjson_decode_rfc6901_segment( seg.c_str(), static_cast<uintptr_t>( seg.size() ) );
}

/// Разбить путь на родительский путь и последний сегмент.
/// Последний сегмент декодируется по RFC 6901.
inline void pjson_split_path( const char* path, std::string& parent, std::string& last )
{
if ( path == nullptr || path[0] == '\0' )
Expand All @@ -33,17 +70,17 @@ inline void pjson_split_path( const char* path, std::string& parent, std::string
if ( pos == std::string::npos )
{
parent = "";
last = full;
last = pjson_decode_rfc6901_segment( full );
}
else if ( pos == 0 )
{
parent = "/";
last = full.substr( 1 );
last = pjson_decode_rfc6901_segment( full.substr( 1 ) );
}
else
{
parent = full.substr( 0, pos );
last = full.substr( pos + 1 );
last = pjson_decode_rfc6901_segment( full.substr( pos + 1 ) );
}
}

Expand Down
2 changes: 1 addition & 1 deletion pjson_db_pmm.h
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ class pjson_db_pmm
if ( seg_len == 0 )
continue;

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

// --- разыменование $ref ---
if ( deref_refs )
Expand Down
20 changes: 6 additions & 14 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 строка дублирования.
**Тесты:** 659 тестов, ~360 000 assertion.
**Тесты:** 676 тестов, ~360 000 assertion.

---

Expand Down Expand Up @@ -70,18 +70,9 @@

---

### Проблема 7: Отсутствие escaping '/' в path-сегментах
### ~~Проблема 7: Отсутствие escaping '/' в path-сегментах~~ ✅

**Файл:** `pjson_db_pmm.h`, метод `_walk_path()`

Символ `/` используется как разделитель пути, но не поддерживается экранирование. Если ключ объекта содержит `/`, обратиться к нему через path-адресацию невозможно:

```cpp
db.put("/config", R"({"a/b": 42})");
db.get("/config/a/b"); // ищет config → "a" → "b" вместо config → "a/b"
```

**Решение:** Поддержать RFC 6901 (JSON Pointer): символ `~` экранируется как `~0`, `/` как `~1`.
**Решено в Этапе 10.2:** Реализована поддержка RFC 6901 (JSON Pointer) для путей. Добавлена функция `pjson_decode_rfc6901_segment()` в `pjson_db_helpers.h`, которая декодирует `~1` → `/` и `~0` → `~`. Декодирование применяется в `_walk_path()` и `pjson_split_path()`. Ключи, содержащие `/` и `~`, теперь доступны через path-адресацию с экранированием. Добавлены 17 тестов.

---

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

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

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

| Дата | Изменение |
|------|-----------|
| 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) |
| 2026-03-22 | Этап 9.3: _free_node_tree и _resolve_refs_in_subtree через pjson_traverse_subtree с visitor-функторами (Issue #191) |
Expand Down
19 changes: 16 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ int main() {
| **Два типа строк** | readonly (`pstringview_pmm`): ключи объектов, пути `$ref`, интернированы, сравнение O(1); readwrite (`PamManager::pstring`): строковые значения JSON, изменяемые на лету |
| **Нет SSO** | Ни `pstringview_pmm`, ни `PamManager::pstring` не используют SSO — все строки хранятся в ПАП (необходимо для сквозного поиска) |
| **jsonRVM-совместимость** | `pstring`-узлы могут модифицироваться непосредственно в БД библиотекой [jsonRVM](https://github.com/netkeep80/jsonRVM); `node_id`-ссылки стабильны при resize array/object |
| **Path-адресация** | Доступ к узлам через строковые пути вида `/a/b/0/c` |
| **Path-адресация** | Доступ к узлам через строковые пути вида `/a/b/0/c`; RFC 6901 escaping (`~1` для `/`, `~0` для `~`) |
| **$ref как указатели** | `{ "$ref": "/path" }` при разборе становится прямым указателем в ПАП |
| **Метрики** | Персистная структура `db_metrics_pmm` в ПАМ; обновляется при каждой мутации; доступ через `/$metrics/...` |
| **pmap-интерфейс** | `operator[]`, `find`, `insert` для доступа по пути без явного указания типа |
Expand Down 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 (659 тестов, ~360 000 assertion) |
| `tests/` | — | Тесты на Catch2 (676 тестов, ~360 000 assertion) |
| `CMakeLists.txt` | — | Система сборки (CMake 3.16+, C++20) |

---
Expand Down Expand Up @@ -195,6 +195,19 @@ node_view age = db.get("/users/alice/age");
// age.as_int() -> 30
```

### RFC 6901 — ключи с `/` и `~`

```cpp
// Ключ "a/b" экранируется как "a~1b" в пути (RFC 6901 JSON Pointer)
db.put("/config/a~1b", 42);
node_view v = db.get("/config/a~1b");
// v.as_int() -> 42; фактический ключ в объекте — "a/b"

// Ключ "x~y" экранируется как "x~0y"
db.put("/data/x~0y", "hello");
// фактический ключ — "x~y"
```

### Работа с `$ref`

```cpp
Expand Down Expand Up @@ -439,7 +452,7 @@ db.put("/copy/name", "Bob");
## Известные ограничения

- **Глобальное состояние PMM** — в одном процессе может быть открыта только одна БД (см. [plan.md](plan.md), Проблема 3); состояние инкапсулировано в `pam_pmm_state`, передача как параметра — в будущих версиях
- **Нет escaping `/` в путях** — ключи объектов, содержащие `/`, недоступны через path-адресацию (см. [plan.md](plan.md), Проблема 7)
- ~~**Нет escaping `/` в путях**~~**Исправлено** в Этапе 10.2: поддержка RFC 6901 (JSON Pointer) — `~1` для `/`, `~0` для `~` в сегментах путей
- ~~**Утечка временных узлов метрик**~~ — **Исправлено** в Этапе 8.4: один pre-allocated узел переиспользуется для всех вызовов метрик
- **Не потокобезопасно** — 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 @@ -38,6 +38,7 @@ set(TEST_SOURCES
test_regression_pmm_types.cpp
test_compat_save_load.cpp
test_pjson_ref_stability.cpp
test_pjson_rfc6901.cpp
)

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