/** * 介质:SharedMemoryBackend(跨实例共享,模拟磁盘持久化) */ import { KVStoreEngine } from '../../src/engine/kvstore_engine'; import { SharedMemoryBackend } from '../../src/engine/kvstore/shared_memory_medium'; import { createSchema } from '../../src/table/schema'; let dbCounter = 0; describe('KVStoreEngine', () => { let engine: KVStoreEngine; let dbName: string; const userSchema = createSchema('users', { id: { type: 'string', primaryKey: true }, name: { type: 'string', required: true }, age: { type: 'number', default: 0 }, email: { type: 'string', unique: true }, }); beforeEach(async () => { dbName = `test-kv-${++dbCounter}-${Math.random().toString(36).slice(2, 6)}`; engine = new KVStoreEngine(new SharedMemoryBackend()); await engine.open(dbName, 1); }); afterEach(async () => { if (engine.isOpen()) { await engine.close(); } }); // ---- 生命周期 ---- describe('生命周期', () => { it('打开和关闭', async () => { expect(engine.isOpen()).toBe(true); await engine.close(); expect(engine.isOpen()).toBe(false); }); it('未打开时操作抛出错误', async () => { await engine.close(); await expect(engine.count('users')).rejects.toThrow('not opened'); }); it('可以重新打开', async () => { await engine.close(); await engine.open(dbName, 2); expect(engine.isOpen()).toBe(true); }); }); // ---- 表管理 ---- describe('表管理', () => { it('创建表', async () => { await engine.createTable(userSchema); expect(await engine.hasTable('users')).toBe(true); const names = await engine.getTableNames(); expect(names).toContain('users'); }); it('重复创建表抛错', async () => { await engine.createTable(userSchema); await expect(engine.createTable(userSchema)).rejects.toThrow('already exists'); }); it('删除表', async () => { await engine.createTable(userSchema); await engine.insert('users', [{ id: '1', name: 'Alice' }]); await engine.dropTable('users'); expect(await engine.hasTable('users')).toBe(false); await expect(engine.count('users')).rejects.toThrow('does not exist'); }); it('获取表结构', async () => { await engine.createTable(userSchema); const schema = await engine.getTableSchema('users'); expect(schema).not.toBeNull(); expect(schema!.columns.id.primaryKey).toBe(true); }); it('schema 持久化:重启后表结构保留', async () => { await engine.createTable(userSchema); await engine.close(); const engine2 = new KVStoreEngine(new SharedMemoryBackend()); await engine2.open(dbName, 1); expect(await engine2.hasTable('users')).toBe(true); const schema = await engine2.getTableSchema('users'); expect(schema!.columns.email.unique).toBe(true); await engine2.close(); }); }); // ---- 插入 ---- describe('插入', () => { beforeEach(async () => { await engine.createTable(userSchema); }); it('插入单行并返回主键', async () => { const pks = await engine.insert('users', [{ id: '1', name: 'Alice', age: 30 }]); expect(pks).toEqual(['1']); expect(await engine.count('users')).toBe(1); }); it('默认值生效', async () => { await engine.insert('users', [{ id: '1', name: 'Alice' }]); const rows = await engine.find('users', { table: 'users' }); expect(rows[0].age).toBe(0); }); it('重复主键抛错', async () => { await engine.insert('users', [{ id: '1', name: 'A' }]); await expect(engine.insert('users', [{ id: '1', name: 'B' }])).rejects.toThrow('Duplicate primary key'); }); it('唯一约束冲突抛错', async () => { await engine.insert('users', [{ id: '1', name: 'A', email: 'a@x.com' }]); await expect(engine.insert('users', [{ id: '2', name: 'B', email: 'a@x.com' }])) .rejects.toThrow('Unique constraint violation'); }); it('必填字段校验', async () => { await expect(engine.insert('users', [{ id: '1' }])).rejects.toThrow('is required'); }); it('批量插入', async () => { const pks = await engine.insert('users', [ { id: '1', name: 'A' }, { id: '2', name: 'B' }, { id: '3', name: 'C' }, ]); expect(pks).toEqual(['1', '2', '3']); }); }); // ---- 查询 ---- describe('查询', () => { beforeEach(async () => { await engine.createTable(userSchema); await engine.insert('users', [ { id: '1', name: 'Alice', age: 30 }, { id: '2', name: 'Bob', age: 25 }, { id: '3', name: 'Carol', age: 35 }, ]); }); it('全表查询', async () => { const rows = await engine.find('users', { table: 'users' }); expect(rows).toHaveLength(3); }); it('WHERE 等值过滤', async () => { const rows = await engine.find('users', { table: 'users', where: { name: 'Bob' } }); expect(rows).toHaveLength(1); expect(rows[0].id).toBe('2'); }); it('WHERE 操作符', async () => { const rows = await engine.find('users', { table: 'users', where: { age: { $gt: 26 } } }); expect(rows.map((r) => r.id).sort()).toEqual(['1', '3']); }); it('ORDER BY', async () => { const rows = await engine.find('users', { table: 'users', orderBy: [{ column: 'age', direction: 'desc' }] }); expect(rows[0].id).toBe('3'); expect(rows[2].id).toBe('2'); }); it('LIMIT / OFFSET', async () => { const rows = await engine.find('users', { table: 'users', limit: 2, offset: 1 }); expect(rows).toHaveLength(2); }); it('列投影', async () => { const rows = await engine.find('users', { table: 'users', columns: ['name'] }); expect(Object.keys(rows[0])).toEqual(['name']); }); }); // ---- 更新 ---- describe('更新', () => { beforeEach(async () => { await engine.createTable(userSchema); await engine.insert('users', [ { id: '1', name: 'Alice', age: 30 }, { id: '2', name: 'Bob', age: 25 }, ]); }); it('按条件更新', async () => { const count = await engine.update('users', { table: 'users', where: { id: '1' } }, { age: 31 }); expect(count).toBe(1); const rows = await engine.find('users', { table: 'users', where: { id: '1' } }); expect(rows[0].age).toBe(31); }); it('无匹配行影响 0 行', async () => { const count = await engine.update('users', { table: 'users', where: { id: 'nope' } }, { age: 1 }); expect(count).toBe(0); }); it('更新主键(旧键删除 + 新键写入)', async () => { const count = await engine.update('users', { table: 'users', where: { id: '1' } }, { id: '10', age: 99 }); expect(count).toBe(1); expect(await engine.count('users')).toBe(2); const rows = await engine.find('users', { table: 'users', where: { id: '10' } }); expect(rows[0].age).toBe(99); expect(await engine.find('users', { table: 'users', where: { id: '1' } })).toHaveLength(0); }); it('更新持久化:重启后保留', async () => { await engine.update('users', { table: 'users', where: { id: '1' } }, { age: 99 }); await engine.close(); const engine2 = new KVStoreEngine(new SharedMemoryBackend()); await engine2.open(dbName, 1); const rows = await engine2.find('users', { table: 'users', where: { id: '1' } }); expect(rows[0].age).toBe(99); await engine2.close(); }); }); // ---- 删除 ---- describe('删除', () => { beforeEach(async () => { await engine.createTable(userSchema); await engine.insert('users', [ { id: '1', name: 'Alice' }, { id: '2', name: 'Bob' }, { id: '3', name: 'Carol' }, ]); }); it('按条件删除', async () => { const count = await engine.delete('users', { table: 'users', where: { id: '1' } }); expect(count).toBe(1); expect(await engine.count('users')).toBe(2); }); it('删除持久化:重启后保留', async () => { await engine.delete('users', { table: 'users', where: { id: '1' } }); await engine.close(); const engine2 = new KVStoreEngine(new SharedMemoryBackend()); await engine2.open(dbName, 1); expect(await engine2.count('users')).toBe(2); await engine2.close(); }); it('级联删除(CASCADE)', async () => { const ordersSchema = createSchema('orders', { id: { type: 'string', primaryKey: true }, user_id: { type: 'string', references: 'users.id', onDelete: 'CASCADE' }, }); await engine.createTable(ordersSchema); await engine.insert('orders', [{ id: 'o1', user_id: '1' }, { id: 'o2', user_id: '2' }]); await engine.delete('users', { table: 'users', where: { id: '1' } }); // 级联:orders 中 user_id=1 的行被删 expect(await engine.count('orders')).toBe(1); // 重启后级联结果持久化 await engine.close(); const engine2 = new KVStoreEngine(new SharedMemoryBackend()); await engine2.open(dbName, 1); expect(await engine2.count('users')).toBe(2); expect(await engine2.count('orders')).toBe(1); await engine2.close(); }); }); // ---- Count ---- describe('Count', () => { beforeEach(async () => { await engine.createTable(userSchema); await engine.insert('users', [ { id: '1', name: 'A', age: 10 }, { id: '2', name: 'B', age: 20 }, { id: '3', name: 'C', age: 30 }, ]); }); it('总数', async () => { expect(await engine.count('users')).toBe(3); }); it('条件计数', async () => { expect(await engine.count('users', { table: 'users', where: { age: { $gte: 20 } } })).toBe(2); }); }); // ---- 事务 ---- describe('事务', () => { beforeEach(async () => { await engine.createTable(userSchema); }); it('commit 后数据可见且持久化', async () => { await engine.beginTransaction(); await engine.insert('users', [{ id: '1', name: 'Alice' }]); await engine.commitTransaction(); expect(await engine.count('users')).toBe(1); await engine.close(); const engine2 = new KVStoreEngine(new SharedMemoryBackend()); await engine2.open(dbName, 1); expect(await engine2.count('users')).toBe(1); await engine2.close(); }); it('rollback 后数据消失', async () => { await engine.beginTransaction(); await engine.insert('users', [{ id: '1', name: 'Alice' }]); await engine.rollbackTransaction(); expect(await engine.count('users')).toBe(0); }); it('事务内读到自己写入的行', async () => { await engine.beginTransaction(); await engine.insert('users', [{ id: '1', name: 'Alice' }]); const rows = await engine.find('users', { table: 'users' }); expect(rows).toHaveLength(1); await engine.rollbackTransaction(); }); it('commit 多表原子(全部生效或全部不生效)', async () => { await engine.createTable(createSchema('orders', { id: { type: 'string', primaryKey: true }, user_id: { type: 'string' }, })); await engine.beginTransaction(); await engine.insert('users', [{ id: '1', name: 'Alice' }]); await engine.insert('orders', [{ id: 'o1', user_id: '1' }]); await engine.commitTransaction(); await engine.close(); const engine2 = new KVStoreEngine(new SharedMemoryBackend()); await engine2.open(dbName, 1); expect(await engine2.count('users')).toBe(1); expect(await engine2.count('orders')).toBe(1); await engine2.close(); }); it('事务中 DDL 支持(create/drop 随 commit 生效)', async () => { const txSchema = createSchema('tx_users', { id: { type: 'string', primaryKey: true }, name: { type: 'string' }, }); await engine.beginTransaction(); await engine.createTable(txSchema); await engine.insert('tx_users', [{ id: '1', name: 'A' }]); await engine.commitTransaction(); expect(await engine.hasTable('tx_users')).toBe(true); expect(await engine.count('tx_users')).toBe(1); // 事务中 drop 表 await engine.beginTransaction(); await engine.dropTable('tx_users'); await engine.commitTransaction(); expect(await engine.hasTable('tx_users')).toBe(false); // 重启后 drop 生效(KV 残留清理) await engine.close(); const engine2 = new KVStoreEngine(new SharedMemoryBackend()); await engine2.open(dbName, 1); expect(await engine2.hasTable('tx_users')).toBe(false); await engine2.close(); }); }); // ---- 持久化与崩溃恢复 ---- describe('持久化与崩溃恢复', () => { it('未 checkpoint 数据重启后经日志重放恢复', async () => { await engine.createTable(userSchema); await engine.insert('users', [ { id: '1', name: 'Alice', age: 30 }, { id: '2', name: 'Bob', age: 25 }, ]); await engine.close(); // 模拟"崩溃后重开"(未 checkpoint,数据在 KVStore 日志) const engine2 = new KVStoreEngine(new SharedMemoryBackend()); await engine2.open(dbName, 1); expect(await engine2.count('users')).toBe(2); const rows = await engine2.find('users', { table: 'users' }); expect(rows.map((r) => r.id).sort()).toEqual(['1', '2']); await engine2.close(); }); it('checkpoint 后重启(快照恢复)', async () => { await engine.createTable(userSchema); await engine.insert('users', [{ id: '1', name: 'Alice' }]); await (engine as any).kv.checkpoint(); await engine.insert('users', [{ id: '2', name: 'Bob' }]); await engine.close(); const engine2 = new KVStoreEngine(new SharedMemoryBackend()); await engine2.open(dbName, 1); expect(await engine2.count('users')).toBe(2); await engine2.close(); }); it('索引列跨重启恢复(查询走索引)', async () => { const idxSchema = createSchema('items', { id: { type: 'string', primaryKey: true }, tag: { type: 'string', index: true }, }); await engine.createTable(idxSchema); await engine.insert('items', [ { id: '1', tag: 'a' }, { id: '2', tag: 'b' }, { id: '3', tag: 'a' }, ]); await engine.close(); const engine2 = new KVStoreEngine(new SharedMemoryBackend()); await engine2.open(dbName, 1); const rows = await engine2.find('items', { table: 'items', where: { tag: 'a' } }); expect(rows).toHaveLength(2); await engine2.close(); }); it('CREATE INDEX 持久化(重启后索引保留)', async () => { await engine.createTable(userSchema); await engine.insert('users', [{ id: '1', name: 'Alice', age: 30 }]); await engine.createIndex('users', 'name'); await engine.close(); const engine2 = new KVStoreEngine(new SharedMemoryBackend()); await engine2.open(dbName, 1); const schema = await engine2.getTableSchema('users'); expect(schema!.columns.name.index).toBe(true); const rows = await engine2.find('users', { table: 'users', where: { name: 'Alice' } }); expect(rows).toHaveLength(1); await engine2.close(); }); it('ALTER TABLE ADD/DROP 持久化', async () => { await engine.createTable(userSchema); await engine.insert('users', [{ id: '1', name: 'Alice', age: 30 }]); await engine.alterTable('users', 'ADD', { name: 'phone', type: 'string' }); await engine.alterTable('users', 'DROP', { name: 'age', type: 'number' }); await engine.close(); const engine2 = new KVStoreEngine(new SharedMemoryBackend()); await engine2.open(dbName, 1); const schema = await engine2.getTableSchema('users'); expect(schema!.columns.phone).toBeDefined(); expect(schema!.columns.age).toBeUndefined(); const rows = await engine2.find('users', { table: 'users' }); expect(rows[0].phone).toBeUndefined(); expect(rows[0].age).toBeUndefined(); await engine2.close(); }); it('元数据 getMeta/setMeta 持久化', async () => { await engine.setMeta('__metona_version', '3'); await engine.close(); const engine2 = new KVStoreEngine(new SharedMemoryBackend()); await engine2.open(dbName, 1); expect(await engine2.getMeta('__metona_version')).toBe('3'); await engine2.close(); }); it('数据损坏自愈:日志损坏 → open 不崩,repair 截断', async () => { await engine.createTable(userSchema); await engine.insert('users', [{ id: '1', name: 'Alice' }]); await engine.close(); // 篡改日志尾部 CRC(模拟最后一条记录写入中断) const medium = new SharedMemoryBackend(); await medium.open(dbName); const log = await medium.read('__kv_log'); expect(log).not.toBeNull(); const corrupted = new Uint8Array(log as ArrayBuffer); corrupted[corrupted.byteLength - 5] ^= 0xff; await medium.write('__kv_log', corrupted.buffer as ArrayBuffer); const engine2 = new KVStoreEngine(new SharedMemoryBackend()); await engine2.open(dbName, 1); // 损坏尾部截断,打开不崩 expect(await engine2.hasTable('users')).toBe(true); await engine2.repair(); expect(await engine2.hasTable('users')).toBe(true); // repair 后新写入正常 await engine2.insert('users', [{ id: '2', name: 'Bob' }]); expect(await engine2.count('users')).toBeGreaterThanOrEqual(1); await engine2.close(); }); }); // ---- 流式查询 ---- describe('流式查询', () => { beforeEach(async () => { await engine.createTable(userSchema); await engine.insert('users', [ { id: '1', name: 'A' }, { id: '2', name: 'B' }, { id: '3', name: 'C' }, ]); }); it('findStream 逐行回调', async () => { const rows: Record[] = []; const count = await engine.findStream!('users', { table: 'users' }, (r) => rows.push(r)); expect(count).toBe(3); expect(rows.map((r) => r.id).sort()).toEqual(['1', '2', '3']); }); }); });