mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 07:20:44 +08:00
- Add SQLAlchemy Core repos, pg adapter/compat, assistant_store factory, Alembic bootstrap and migration/cutover scripts. - Harden chat_message writes (NUL scrub for PG), turn_uuid on attempt failure, WS turn_runner fallbacks and gateway executed_turn_uuid init. - start_gateway: log paths, PS7 stderr handling via cmd, background stdout/stderr redirect; runtime assistant_runtime_log_dir export. - Ops: clear_all_chat_sessions with PG-only --postgresql and env-gated wipe; clear_postgres_chat_sessions.ps1. - Tests: SA repos, pg compat, persist fallback, smoke env isolation; CI and docs touch-ups. Co-authored-by: Cursor <cursoragent@cursor.com>
72 lines
2.3 KiB
Python
72 lines
2.3 KiB
Python
"""SQLAlchemy Engine factory for assistant DB (SQLite or PostgreSQL)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from functools import lru_cache
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from sqlalchemy import create_engine, event
|
|
from sqlalchemy.engine import Engine
|
|
from sqlalchemy.pool import NullPool
|
|
|
|
from svc.config.database import assistant_sqlalchemy_url
|
|
|
|
|
|
def _sqlite_sa_url_from_os_path(path: str) -> str:
|
|
"""Build the same ``sqlite+pysqlite:///...`` URL shape as :func:`svc.config.database.assistant_sqlalchemy_url`."""
|
|
p = Path(path).resolve().as_posix()
|
|
return f"sqlite+pysqlite:///{p}"
|
|
|
|
|
|
def _register_sqlite_pragmas(eng: Engine) -> None:
|
|
@event.listens_for(eng, "connect")
|
|
def _sqlite_pragmas(dbapi_conn: Any, _record: Any) -> None:
|
|
cur = dbapi_conn.cursor()
|
|
cur.execute("PRAGMA foreign_keys = ON;")
|
|
cur.execute("PRAGMA journal_mode = WAL;")
|
|
cur.execute("PRAGMA synchronous = NORMAL;")
|
|
cur.execute("PRAGMA busy_timeout = 30000;")
|
|
cur.close()
|
|
|
|
|
|
@lru_cache(maxsize=64)
|
|
def _engine_for_url(url: str) -> Engine:
|
|
"""One engine per URL (process-wide)."""
|
|
pool_kw: dict[str, Any] = {}
|
|
if url.startswith("sqlite"):
|
|
pool_kw["poolclass"] = NullPool
|
|
else:
|
|
pool_kw["pool_pre_ping"] = True
|
|
eng = create_engine(url, future=True, **pool_kw)
|
|
if url.startswith("sqlite"):
|
|
_register_sqlite_pragmas(eng)
|
|
return eng
|
|
|
|
|
|
def get_assistant_engine() -> Engine:
|
|
"""Engine for the current env-selected assistant DB (Alembic, ``get_assistant_store`` SQLite path)."""
|
|
return _engine_for_url(assistant_sqlalchemy_url())
|
|
|
|
|
|
def engine_for_sqlite_file(path: str) -> Engine:
|
|
"""Engine for a specific SQLite file (e.g. ``SqliteStore('/tmp/x.sqlite')`` without env ``DB_PATH``)."""
|
|
return _engine_for_url(_sqlite_sa_url_from_os_path(path))
|
|
|
|
|
|
def clear_assistant_engine_cache() -> None:
|
|
"""Drop cached engines (e.g. tests that delete temp DB files must call this before removing the directory)."""
|
|
_engine_for_url.cache_clear()
|
|
try:
|
|
from svc.persistence.assistant_store import reset_assistant_store_singleton
|
|
|
|
reset_assistant_store_singleton()
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
__all__ = [
|
|
"clear_assistant_engine_cache",
|
|
"engine_for_sqlite_file",
|
|
"get_assistant_engine",
|
|
]
|