Forge

docs/ / adr/0004-sql-layer-and-casa-journal.md · branch master

ADR-0004: SQL layer (WitSQL) for pywitdb — путь к CASA

Статус: принято
Дата: 2026-06-06
Репозиторий: AI-Guiders/pywitdb
Движок: WitDatabaseOutWit.Database (WitSqlEngine)
Связано: ADR-0001, ADR-0002, ADR-0003
Потребитель (follow-up): casa-ontology-payloadagent_journal.db, agent_field.db


Решение (одной фразой)

SQL в pywitdb через WitSqlEngine (пакет OutWit.Database): сначала bridge (op: sql_exec / NDJSON rows), затем native C ABI v2; Python — тонкий фасад (connect / execute / fetchall), совместимый с паттернами sqlite3 в CASA. Миграция CASA — после стабильного SQL-контракта, не до.


Контекст

Факт Следствие
pywitdb v1 (ADR-0003) — только KV + txn agent_journal.py (SQL append + GROUP BY + prune) на KV без боли не переезжает
CASA field (agent_field.db) — bulk routes + chunk Теоретически KV, но цель — единый .witdb, не два движка
WitSQL ≠ SQLite Нужна матрица совместимости, не слепой перенос DDL
Native win-x64 работает (ADR-0003) SQL-native тяжелее (Engine + AOT trim), bridge — быстрый proof

Primary driver миграции CASA: единый стек .witdb, шифрование, cross-lang с .NET — не обещание «быстрее sqlite» на journal workload. Performance — validate (бенч после контракта), not assume.


Архитектура слоёв

casa/domain/agent_journal.py  (sqlite3 → pywitdb.sql)
        ↓
pywitdb.sql.Connection / Cursor   ← Python фасад
        ↓
┌───────────────────┬───────────────────────┐
│ bridge            │ native (ABI v2)       │
│ op: sql_exec      │ witdb_sql_exec(...)   │
│ WitSqlEngine      │ WitSqlEngine in AOT   │
└───────────────────┴───────────────────────┘
        ↓
WitDatabase (Core) + WitSqlEngine (OutWit.Database)

Зависимость: OutWit.Database.Core (store) + OutWit.Database (SQL engine). Native project сегодня — только Core; SQL добавляет ссылку на Engine и расширяет trim roots.


Python API (v1 SQL)

Модуль: pywitdb.sql (или pywitdb._sql + re-export).

import pywitdb.sql as wsql

with wsql.connect("agent_journal.witdb", password=None, backend="auto") as conn:
    conn.execute(_CREATE_SQL)
    conn.execute(
        "INSERT INTO skill_trace (at, tick, goal_id, skill, plan_step, outcome, payload) "
        "VALUES (?, ?, ?, ?, ?, ?, ?)",
        (at, tick, goal_id, skill, plan_step, outcome, payload_json),
    )
    rows = conn.execute(
        "SELECT payload FROM skill_trace WHERE goal_id LIKE ? ORDER BY id DESC LIMIT ?",
        (f"%{goal_id}%", limit),
    ).fetchall()
    last_id = conn.last_insert_rowid()
Метод Семантика
connect(path, password=..., backend="auto") open store + attach WitSqlEngine
Connection.execute(sql, params=()) DDL/DML; returns Cursor
Cursor.fetchall() / fetchone() rows as tuple or Row (mapping по имени колонки — v1.1)
Connection.commit() / rollback() txn boundary (map на WitSqlEngine / Core txn)
Connection.last_insert_rowid() LastInsertRowId после INSERT
context manager close + dispose engine

Параметры: позиционные ? в Python → @p0, @p1 или named @name в WitSqlEngine (зафиксировать в реализации; предпочтение ? для sqlite-порта CASA).

Non-goals SQL v1: DB-API 2.0 полнота, async cursor, SQLAlchemy dialect, multi-connection pool.


Bridge protocol (расширение ADR-0001)

После open на том же subprocess:

{"id": 10, "op": "sql_exec", "sql": "CREATE TABLE IF NOT EXISTS skill_trace (...)"}
{"id": 10, "ok": true, "rows_affected": 0}

{"id": 11, "op": "sql_query", "sql": "SELECT COUNT(*) AS c FROM skill_trace", "params": []}
{"id": 11, "ok": true, "columns": ["c"], "rows": [[42]]}

{"id": 12, "op": "sql_exec", "sql": "INSERT INTO ...", "params": ["2026-...", 1, "g1", ...]}
{"id": 12, "ok": true, "rows_affected": 1, "last_insert_rowid": 7}
op Назначение
sql_exec DDL, DML без result set
sql_query SELECT → columns + rows (JSON-safe types)
sql_batch optional v1.1 — несколько stmt в одной txn

Ошибки SQL: error.code = sql_error, message от движка.

Bridge csproj: + ProjectReference на OutWit.Database (Engine), не только Core.


Native C ABI (v2 — расширение ADR-0003)

Вариант A (принят): тот же WITDB_ABI_VERSION=1, новые exports (minor-compatible):

WitDbStatus witdb_sql_exec(
    uintptr_t db,
    const char* sql,
    const char* params_json,  // NULL or JSON array of values
    int64_t* out_last_rowid,
    int32_t* out_rows_affected);

WitDbStatus witdb_sql_query(
    uintptr_t db,
    const char* sql,
    const char* params_json,
    char** out_result_json,   // {"columns":[...],"rows":[...]}
    uint32_t* out_result_len);

out_result_json — caller frees via witdb_buffer_free. Вызовы через тот же CLR worker, что ADR-0003 (WitDbClrThread).

Вариант B (отклонён): bump WITDB_ABI_VERSION=2 только из-за SQL — избыточно, если KV exports не меняются.

Native csproj: + Engine reference; trimming.xml расширить под WitSqlEngine / parser.


CASA journal — матрица совместимости (acceptance)

Источник: casa/domain/agent_journal.py (_CREATE_SQL + запросы).

Возможность SQLite в CASA WitSQL Действие
CREATE TABLE IF NOT EXISTS как есть
CREATE INDEX IF NOT EXISTS как есть
INTEGER PRIMARY KEY AUTOINCREMENT ⚠️ явный AUTOINCREMENT поддерживается; в тестах движка чаще BIGINT (implicit autoincrement на Int64); gate — contract test
TEXT, INTEGER TEXT, INT / INTEGER
INSERT + lastrowid LastInsertRowId
ORDER BY id DESC LIMIT ? параметризованный LIMIT
GROUP BY + COUNT(*) проверить в contract test
LIKE ? contract test
DELETE ... WHERE id NOT IN (SELECT id ... ORDER BY ... LIMIT ?) SQLite-compat fix: nested subqueries use queryExpression (parser + engine test WitSqlEngineSqlitePruneTests)
sqlite3.Row / dict by name ⚠️ Python Row mapping в фасаде

Contract test (gate перед CASA ADR):
tests/contract/test_casa_journal_sql.py — поднять schema journal на witdb (bridge), прогнать representative ops из agent_journal.py (insert, prune, aggregate, query_skill_trace).


CASA field (фаза 2, после journal SQL)

agent_field.db можно:

  • A) оставить на KV pywitdb (уже близко к модели keys), или
  • B) перевести на SQL tables (meta, routes, chunks) в том же .witdb файле.

Решение — в отдельном ADR casa-ontology-payload после green journal contract.


Порядок реализации

# Задача Репо
1 Bridge: sql_exec / sql_query + Engine ref pywitdb bridge/WitDbBridge
2 pywitdb.sql фасад на bridge pywitdb
3 Contract: CASA journal schema + ops pywitdb tests/contract/
4 Native: witdb_sql_* + trim roots WitDatabase OutWit.Database.Native
5 pywitdb.sql backend native pywitdb
6 Бенч journal vs sqlite (optional report) pywitdb или casa
7 ADR CASA + миграция agent_journal.db.witdb casa-ontology-payload

Последствия

Положительные

  • CASA journal переносится без переписывания на KV.
  • Один .witdb + шифрование + тот же reopen (ADR-0002).
  • Bridge даёт SQL до готовности native AOT matrix.

Отрицательные / риски

  • Engine в NativeAOT — больший артефакт и trim surface.
  • Диалект: не все sqlite-конструкты; prune-subquery — точка риска.
  • Два SQL path (bridge/native) — parity tests обязательны.

Non-goals

  • SQLAlchemy dialect (ADR позже).
  • asyncio SQL (обёртка to_thread — вне scope ADR-0004).
  • Замена sqlite в других проектах репо (IncomeCascade и т.д.).

Ссылки

  • WitSQL engine: witdatabase/Sources/Engine/OutWit.Database/
  • CASA journal: casa-ontology-payload/casa/domain/agent_journal.py
  • CASA field: casa-ontology-payload/casa/domain/agent_field.py
  • Native worker: ADR-0003 WitDbClrThread
View only · write via MCP/CIDE