Spaces:
Sleeping
Sleeping
| """ | |
| permanence.world.db β mock SQL database with transactional reversibility. | |
| This module simulates the operational semantics of DDL and DML operations | |
| that matter for reversibility prediction. It is not a SQL engine; it models: | |
| * Tables, rows, primary keys | |
| * Transactions with BEGIN / COMMIT / ROLLBACK | |
| * A write-ahead log for committed changes | |
| * Named snapshots (backups) | |
| All state is in-memory Python. No subprocess, no network, no file I/O. | |
| Reversibility classes encoded: | |
| R1 ``SELECT`` β read-only | |
| R2 ``INSERT``/``UPDATE``/``DELETE`` inside txn β rolled back trivially | |
| R3 ``COMMIT`` of a DML txn β reversible via WAL replay | |
| and/or a prior backup | |
| R4 ``DROP TABLE`` when a backup exists β reversible from backup | |
| R5 ``DROP TABLE`` with no backup, or | |
| ``TRUNCATE`` + ``COMMIT`` with no backup β unrecoverable | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| from typing import Any, Dict, List, Optional, Tuple | |
| import copy | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Data model | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class Table: | |
| name: str | |
| primary_key: str | |
| rows: Dict[Any, Dict[str, Any]] = field(default_factory=dict) # pk β row | |
| def n_rows(self) -> int: | |
| return len(self.rows) | |
| class TxnOp: | |
| op: str # "insert" | "update" | "delete" | "drop" | "truncate" | |
| table: str | |
| before: Optional[Any] | |
| after: Optional[Any] | |
| class DBResult: | |
| ok: bool | |
| message: str | |
| r_level: int | |
| rows_affected: int = 0 | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # The mock database | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class MockDatabase: | |
| """In-memory relational store with transactions and backups. | |
| State layers: | |
| * ``tables`` β the committed state | |
| * ``txn_ops`` β operations staged in the current transaction | |
| * ``txn_active`` β whether BEGIN has been issued | |
| * ``wal`` β ordered list of committed TxnOp batches | |
| * ``backups`` β named snapshots of the full ``tables`` map | |
| Reversibility is derived from whether a prior state is still reachable | |
| β via ROLLBACK (uncommitted), via WAL replay (committed), or via a | |
| backup (DROP/TRUNCATE recovery). | |
| """ | |
| def __init__(self) -> None: | |
| self.tables: Dict[str, Table] = {} | |
| self.txn_ops: List[TxnOp] = [] | |
| self.txn_active: bool = False | |
| self.wal: List[List[TxnOp]] = [] | |
| self.backups: Dict[str, Dict[str, Table]] = {} | |
| # βββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _require_table(self, name: str) -> Optional[Table]: | |
| return self.tables.get(name) | |
| def _record_op(self, op: TxnOp) -> None: | |
| if self.txn_active: | |
| self.txn_ops.append(op) | |
| else: | |
| # Implicit autocommit: single op goes straight to WAL | |
| self.wal.append([op]) | |
| def _backup_contains_table(self, name: str) -> bool: | |
| return any(name in snap for snap in self.backups.values()) | |
| # βββ DDL ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def create_table(self, name: str, primary_key: str) -> DBResult: | |
| if name in self.tables: | |
| return DBResult(False, f"table exists: {name}", r_level=1) | |
| self.tables[name] = Table(name=name, primary_key=primary_key) | |
| return DBResult(True, f"created table {name}", r_level=2) | |
| def drop_table(self, name: str) -> DBResult: | |
| t = self._require_table(name) | |
| if t is None: | |
| return DBResult(False, f"no such table: {name}", r_level=1) | |
| before = copy.deepcopy(t) | |
| del self.tables[name] | |
| op = TxnOp(op="drop", table=name, before=before, after=None) | |
| self._record_op(op) | |
| # DROP cannot be rolled back inside most SQL engines, but we model | |
| # recovery via backup: R4 if a snapshot contains it, R5 otherwise. | |
| r = 4 if self._backup_contains_table(name) else 5 | |
| return DBResult( | |
| True, | |
| f"dropped {name} ({t.n_rows()} rows, {'recoverable from backup' if r == 4 else 'UNRECOVERABLE'})", | |
| r_level=r, | |
| rows_affected=t.n_rows(), | |
| ) | |
| def truncate(self, name: str) -> DBResult: | |
| t = self._require_table(name) | |
| if t is None: | |
| return DBResult(False, f"no such table: {name}", r_level=1) | |
| n = t.n_rows() | |
| before = copy.deepcopy(t.rows) | |
| t.rows = {} | |
| op = TxnOp(op="truncate", table=name, before=before, after=None) | |
| if self.txn_active: | |
| self.txn_ops.append(op) | |
| return DBResult(True, f"truncated {name} (uncommitted)", r_level=2, rows_affected=n) | |
| # Auto-committed truncate: recovery depends on backup | |
| self.wal.append([op]) | |
| r = 4 if self._backup_contains_table(name) else 5 | |
| return DBResult( | |
| True, | |
| f"truncated {name} ({'backed up' if r == 4 else 'UNRECOVERABLE'})", | |
| r_level=r, | |
| rows_affected=n, | |
| ) | |
| # βββ DML ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def insert(self, table: str, row: Dict[str, Any]) -> DBResult: | |
| t = self._require_table(table) | |
| if t is None: | |
| return DBResult(False, f"no such table: {table}", r_level=1) | |
| pk = row.get(t.primary_key) | |
| if pk is None: | |
| return DBResult(False, f"missing primary key {t.primary_key}", r_level=1) | |
| if pk in t.rows: | |
| return DBResult(False, f"duplicate pk: {pk}", r_level=1) | |
| t.rows[pk] = dict(row) | |
| self._record_op(TxnOp(op="insert", table=table, before=None, after=pk)) | |
| # Inside a txn this is R2; autocommitted it becomes R3 (reversible | |
| # via WAL replay to a snapshot, but not trivially). | |
| r = 2 if self.txn_active else 3 | |
| return DBResult(True, f"inserted 1 into {table}", r_level=r, rows_affected=1) | |
| def update(self, table: str, pk: Any, updates: Dict[str, Any]) -> DBResult: | |
| t = self._require_table(table) | |
| if t is None: | |
| return DBResult(False, f"no such table: {table}", r_level=1) | |
| if pk not in t.rows: | |
| return DBResult(False, f"no row with pk={pk}", r_level=1) | |
| before = copy.deepcopy(t.rows[pk]) | |
| t.rows[pk].update(updates) | |
| self._record_op(TxnOp(op="update", table=table, before=before, after=pk)) | |
| r = 2 if self.txn_active else 3 | |
| return DBResult(True, f"updated pk={pk} in {table}", r_level=r, rows_affected=1) | |
| def delete(self, table: str, pk: Any) -> DBResult: | |
| t = self._require_table(table) | |
| if t is None: | |
| return DBResult(False, f"no such table: {table}", r_level=1) | |
| if pk not in t.rows: | |
| return DBResult(False, f"no row with pk={pk}", r_level=1) | |
| before = t.rows.pop(pk) | |
| self._record_op(TxnOp(op="delete", table=table, before=before, after=None)) | |
| r = 2 if self.txn_active else 3 | |
| return DBResult(True, f"deleted pk={pk} from {table}", r_level=r, rows_affected=1) | |
| def select(self, table: str, pk: Optional[Any] = None) -> DBResult: | |
| t = self._require_table(table) | |
| if t is None: | |
| return DBResult(False, f"no such table: {table}", r_level=1) | |
| if pk is not None: | |
| if pk not in t.rows: | |
| return DBResult(False, f"no row with pk={pk}", r_level=1) | |
| return DBResult(True, str(t.rows[pk]), r_level=1, rows_affected=1) | |
| return DBResult(True, f"{t.n_rows()} rows", r_level=1, rows_affected=t.n_rows()) | |
| # βββ Transactions βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def begin(self) -> DBResult: | |
| if self.txn_active: | |
| return DBResult(False, "transaction already active", r_level=1) | |
| self.txn_active = True | |
| self.txn_ops = [] | |
| return DBResult(True, "BEGIN", r_level=1) | |
| def commit(self) -> DBResult: | |
| if not self.txn_active: | |
| return DBResult(False, "no active transaction", r_level=1) | |
| ops = self.txn_ops | |
| self.txn_ops = [] | |
| self.txn_active = False | |
| if ops: | |
| self.wal.append(ops) | |
| # Commit of DML is R3 by default (WAL replay possible but non-trivial); | |
| # commit of a DROP/TRUNCATE escalates based on backup presence. | |
| highest_r = 3 | |
| for op in ops: | |
| if op.op in ("drop", "truncate"): | |
| if not self._backup_contains_table(op.table): | |
| highest_r = max(highest_r, 5) | |
| else: | |
| highest_r = max(highest_r, 4) | |
| return DBResult(True, f"COMMIT ({len(ops)} ops)", r_level=highest_r) | |
| def rollback(self) -> DBResult: | |
| if not self.txn_active: | |
| return DBResult(False, "no active transaction", r_level=1) | |
| # Replay txn_ops in reverse to undo them on ``self.tables``. | |
| for op in reversed(self.txn_ops): | |
| t = self.tables.get(op.table) | |
| if op.op == "insert" and t is not None and op.after in t.rows: | |
| del t.rows[op.after] | |
| elif op.op == "update" and t is not None and op.before is not None: | |
| t.rows[op.after] = op.before | |
| elif op.op == "delete" and t is not None and op.before is not None: | |
| t.rows[op.before[t.primary_key]] = op.before | |
| elif op.op == "drop" and op.before is not None: | |
| self.tables[op.table] = op.before | |
| elif op.op == "truncate" and op.before is not None and t is not None: | |
| t.rows = dict(op.before) | |
| self.txn_ops = [] | |
| self.txn_active = False | |
| return DBResult(True, "ROLLBACK", r_level=2) | |
| # βββ Backups ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def snapshot(self, snap_id: str) -> DBResult: | |
| self.backups[snap_id] = { | |
| n: Table(name=n, primary_key=t.primary_key, rows=copy.deepcopy(t.rows)) | |
| for n, t in self.tables.items() | |
| } | |
| return DBResult(True, f"snapshot {snap_id} ({len(self.tables)} tables)", r_level=2) | |
| def restore(self, snap_id: str) -> DBResult: | |
| if snap_id not in self.backups: | |
| return DBResult(False, f"no such snapshot: {snap_id}", r_level=1) | |
| self.tables = { | |
| n: Table(name=t.name, primary_key=t.primary_key, rows=dict(t.rows)) | |
| for n, t in self.backups[snap_id].items() | |
| } | |
| return DBResult(True, f"restored from {snap_id}", r_level=2) | |
| # βββ Introspection ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def summary(self) -> Dict[str, int]: | |
| return { | |
| "tables": len(self.tables), | |
| "rows": sum(t.n_rows() for t in self.tables.values()), | |
| "wal_entries": len(self.wal), | |
| "backups": len(self.backups), | |
| "txn_active": int(self.txn_active), | |
| } | |