/** * metona-sqlark Engine Interface — 存储引擎抽象接口 * @module engine/interface */ import type { QueryPlan, TableSchema } from '../constants'; // --------------------------------------------------------------------------- // v0.8.0: 行所有权(row ownership)约定 // --------------------------------------------------------------------------- /** * 深拷贝一行,使调用方**无法通过修改返回值改写存储**。 * * 为什么必须做(审计实测):Memory/KVStore/Hybrid 三个引擎此前把内部行对象 * **直接**交给调用方: * const rows = await db.query('SELECT * FROM t'); * rows[0].tag = 'HACKED'; // 存储被改写 * 再查 WHERE tag='HACKED' → 0 行;WHERE tag='x' → 0 行 * 即调用方一次无意的原地修改就能让索引与行失配、该行永久查不出来(Aria 因为是 * 反序列化路径反而幸免,于是又成了跨引擎行为差异)。 * * 约定(写入 interface 文档,所有引擎必须遵守): * **读出的行是副本,写入接收的行也是副本** —— 引擎不得把内部行对象暴露给外部, * 也不得持有调用方传入的行对象引用。 * * 实现说明:结构化克隆可用时优先使用(正确处理 Date/嵌套对象/循环引用); * 存储层写入的行已经过 validateRow 的 JSON 安全性检查,因此退化路径也是安全的。 */ export function cloneRow>(row: T): T { if (row === null || typeof row !== 'object') return row; if (typeof structuredClone === 'function') { try { return structuredClone(row); } catch { // 含不可克隆值(函数/Proxy)时退化为逐层复制 } } return cloneRowFallback(row) as T; } /** 退化实现:递归复制普通对象与数组(保留 Date) */ function cloneRowFallback(value: unknown): unknown { if (value === null || typeof value !== 'object') return value; if (value instanceof Date) return new Date(value.getTime()); if (Array.isArray(value)) return value.map((v) => cloneRowFallback(v)); if (value instanceof Uint8Array) return new Uint8Array(value); if (value instanceof ArrayBuffer) return value.slice(0); const out: Record = {}; for (const [k, v] of Object.entries(value as Record)) { out[k] = cloneRowFallback(v); } return out; } /** 批量深拷贝 */ export function cloneRows>(rows: T[]): T[] { return rows.map((r) => cloneRow(r)); } // --------------------------------------------------------------------------- // IStorageEngine — 所有存储引擎必须实现的接口 // --------------------------------------------------------------------------- export interface IStorageEngine { /** 引擎名称 */ readonly name: string; /** 打开数据库 */ open(dbName: string, version: number): Promise; /** 关闭数据库 */ close(): Promise; /** 检查数据库是否已打开 */ isOpen(): boolean; /** 创建表 */ createTable(schema: TableSchema): Promise; /** 删除表 */ dropTable(tableName: string): Promise; /** 检查表是否存在 */ hasTable(tableName: string): Promise; /** 获取所有表名 */ getTableNames(): Promise; /** 获取表结构 */ getTableSchema(tableName: string): Promise; /** 插入行,返回主键值列表 */ insert(tableName: string, rows: Record[]): Promise; /** 查询行 */ find(tableName: string, query: QueryPlan): Promise[]>; /** v0.4.0: 流式查询 — 逐行回调扫描(有 where/limit/projection,无 orderBy 语义;有 orderBy 时实现可回退物化) */ findStream?(tableName: string, query: QueryPlan, onRow: (row: Record) => void): Promise; /** 更新行,返回影响行数 */ update(tableName: string, query: QueryPlan, updates: Record): Promise; /** 删除行,返回影响行数 */ delete(tableName: string, query: QueryPlan): Promise; /** 计数 */ count(tableName: string, query?: QueryPlan): Promise; /** 清空表数据(保留结构) */ clear(tableName: string): Promise; /** v0.4.1: ALTER TABLE(可选)— 引擎级结构变更(Aria 需重写存储行,其余引擎走 Executor 通用路径) */ alterTable?(tableName: string, action: 'ADD' | 'DROP', column: import('../constants').ColumnDef & { name: string }): Promise; // ---- 动态索引(可选,v0.3.0) ---- /** 创建二级索引(CREATE INDEX) */ createIndex?(tableName: string, column: string, unique?: boolean): Promise; /** 删除二级索引(DROP INDEX) */ dropIndex?(tableName: string, column: string, indexName?: string): Promise; // ---- 事务 ---- /** 开始事务 */ beginTransaction(): Promise; /** 提交事务 */ commitTransaction(): Promise; /** 回滚事务 */ rollbackTransaction(): Promise; // ---- Savepoint (可选) ---- /** 创建 Savepoint */ savepoint?(name: string): Promise; /** 回滚到 Savepoint */ rollbackToSavepoint?(name: string): Promise; /** 释放 Savepoint */ releaseSavepoint?(name: string): Promise; // ---- 备份 (可选) ---- /** 在线备份:导出全库一致性快照 */ backup?(): Promise[]>>; // ---- 自愈/重置 (可选,v0.4.2-fix) ---- /** 崩溃恢复自愈:校验并清理损坏数据、恢复一致性(检测到异常后调用,无需删库重建) */ repair?(): Promise; /** 清空全部数据与表结构(保留库本身,供演示页刷新/重建用) */ clearAll?(): Promise; /** 读取库内元数据(迁移版本持久化用) */ getMeta?(key: string): Promise; /** 写入库内元数据(迁移版本持久化用) */ setMeta?(key: string, value: string): Promise; }