/** * v0.4.1 测试 — AriaEngine 外键级联 + clearAll 重置 */ import { AriaEngine } from '../src/engine/aria/index'; import { installOPFSMock } from './helpers/opfs-mock'; beforeEach(() => { installOPFSMock(new Map()); }); const mkEngine = async (name: string): Promise => { const e = new AriaEngine({ storageBackend: 'opfs', walSyncMode: 'full' }); await e.open(`cascade-${name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, 1); return e; }; describe('[v0.4.1] AriaEngine 外键级联', () => { test('CASCADE:删除主表行时级联删除引用行', async () => { const e = await mkEngine('cas'); await e.createTable({ name: 'users', columns: { id: { type: 'string', primaryKey: true } } }); await e.createTable({ name: 'orders', columns: { id: { type: 'string', primaryKey: true }, user_id: { type: 'string', references: 'users.id', onDelete: 'CASCADE' } } }); await e.insert('users', [{ id: 'u1' }, { id: 'u2' }]); await e.insert('orders', [{ id: 'o1', user_id: 'u1' }, { id: 'o2', user_id: 'u1' }, { id: 'o3', user_id: 'u2' }]); const count = await e.delete('users', { table: 'users', where: { id: 'u1' } }); expect(count).toBe(3); // u1 + o1 + o2 expect(await e.count('users')).toBe(1); expect(await e.count('orders')).toBe(1); const rest = await e.find('orders', { table: 'orders' }); expect(rest[0].user_id).toBe('u2'); await e.close(); }); test('CASCADE:多层递归(users → orders → order_items)', async () => { const e = await mkEngine('cas2'); await e.createTable({ name: 'users', columns: { id: { type: 'string', primaryKey: true } } }); await e.createTable({ name: 'orders', columns: { id: { type: 'string', primaryKey: true }, user_id: { type: 'string', references: 'users.id', onDelete: 'CASCADE' } } }); await e.createTable({ name: 'order_items', columns: { id: { type: 'string', primaryKey: true }, order_id: { type: 'string', references: 'orders.id', onDelete: 'CASCADE' } } }); await e.insert('users', [{ id: 'u1' }]); await e.insert('orders', [{ id: 'o1', user_id: 'u1' }]); await e.insert('order_items', [{ id: 'i1', order_id: 'o1' }, { id: 'i2', order_id: 'o1' }]); const count = await e.delete('users', { table: 'users', where: { id: 'u1' } }); expect(count).toBe(4); // u1 + o1 + i1 + i2 expect(await e.count('order_items')).toBe(0); await e.close(); }); test('SET NULL:删除主表行时引用列置 null', async () => { const e = await mkEngine('sn'); await e.createTable({ name: 'users', columns: { id: { type: 'string', primaryKey: true } } }); await e.createTable({ name: 'orders', columns: { id: { type: 'string', primaryKey: true }, user_id: { type: 'string', references: 'users.id', onDelete: 'SET NULL' } } }); await e.insert('users', [{ id: 'u1' }]); await e.insert('orders', [{ id: 'o1', user_id: 'u1' }]); const count = await e.delete('users', { table: 'users', where: { id: 'u1' } }); expect(count).toBe(1); // 仅 u1,引用行保留 const rows = await e.find('orders', { table: 'orders' }); expect(rows).toHaveLength(1); expect(rows[0].user_id).toBeNull(); await e.close(); }); test('RESTRICT:存在引用行时禁止删除', async () => { const e = await mkEngine('res'); await e.createTable({ name: 'users', columns: { id: { type: 'string', primaryKey: true } } }); await e.createTable({ name: 'orders', columns: { id: { type: 'string', primaryKey: true }, user_id: { type: 'string', references: 'users.id', onDelete: 'RESTRICT' } } }); await e.insert('users', [{ id: 'u1' }]); await e.insert('orders', [{ id: 'o1', user_id: 'u1' }]); await expect(e.delete('users', { table: 'users', where: { id: 'u1' } })).rejects.toThrow('Cannot delete'); // 数据未被删除 expect(await e.count('users')).toBe(1); expect(await e.count('orders')).toBe(1); await e.close(); }); test('CASCADE 同时清理二级索引(删除后按索引查不到)', async () => { const e = await mkEngine('cidx'); await e.createTable({ name: 'users', columns: { id: { type: 'string', primaryKey: true } } }); await e.createTable({ name: 'orders', columns: { id: { type: 'string', primaryKey: true }, user_id: { type: 'string', index: true, references: 'users.id', onDelete: 'CASCADE' } } }); await e.insert('users', [{ id: 'u1' }]); await e.insert('orders', [{ id: 'o1', user_id: 'u1' }]); await e.delete('users', { table: 'users', where: { id: 'u1' } }); const rows = await e.find('orders', { table: 'orders', where: { user_id: 'u1' } }); expect(rows).toHaveLength(0); await e.close(); }); test('事务内级联删除可提交/回滚', async () => { const e = await mkEngine('ctx'); await e.createTable({ name: 'users', columns: { id: { type: 'string', primaryKey: true } } }); await e.createTable({ name: 'orders', columns: { id: { type: 'string', primaryKey: true }, user_id: { type: 'string', references: 'users.id', onDelete: 'CASCADE' } } }); await e.insert('users', [{ id: 'u1' }]); await e.insert('orders', [{ id: 'o1', user_id: 'u1' }]); // rollback:级联删除被回滚 await e.beginTransaction(); await e.delete('users', { table: 'users', where: { id: 'u1' } }); await e.rollbackTransaction(); expect(await e.count('users')).toBe(1); expect(await e.count('orders')).toBe(1); // commit:级联删除生效 await e.beginTransaction(); await e.delete('users', { table: 'users', where: { id: 'u1' } }); await e.commitTransaction(); expect(await e.count('users')).toBe(0); expect(await e.count('orders')).toBe(0); await e.close(); }); }); describe('[v0.4.1] AriaEngine clearAll', () => { test('clearAll 清空全部表与数据,实例可继续使用', async () => { const e = await mkEngine('clr'); await e.createTable({ name: 'users', columns: { id: { type: 'string', primaryKey: true }, name: { type: 'string', index: true } } }); await e.insert('users', [{ id: '1', name: 'Alice' }]); await e.clearAll(); expect(await e.getTableNames()).toHaveLength(0); // 实例可继续建表使用 await e.createTable({ name: 't2', columns: { id: { type: 'string', primaryKey: true } } }); await e.insert('t2', [{ id: 'x' }]); expect(await e.count('t2')).toBe(1); await e.close(); }); test('clearAll 后重启(模拟刷新)无残留数据', async () => { const name = `clr2-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; let e = new AriaEngine({ storageBackend: 'opfs', walSyncMode: 'full' }); await e.open(name, 1); await e.createTable({ name: 't', columns: { id: { type: 'string', primaryKey: true } } }); await e.insert('t', [{ id: '1' }]); await e.clearAll(); await e.close(); // 重新打开(模拟页面刷新):不应有残留表 e = new AriaEngine({ storageBackend: 'opfs', walSyncMode: 'full' }); await e.open(name, 1); expect(await e.getTableNames()).toHaveLength(0); await e.close(); }); }); describe('[v0.4.1] AriaEngine ALTER TABLE', () => { test('DROP COLUMN 真正清除存储中的列值(find 副本不再残留)', async () => { const e = await mkEngine('alt'); await e.createTable({ name: 'users', columns: { id: { type: 'string', primaryKey: true }, name: { type: 'string' }, email: { type: 'string' }, age: { type: 'number', default: 0 } } }); await e.insert('users', [{ id: '1', name: 'Alice', email: 'a@x.com', age: 30 }]); await e.alterTable('users', 'ADD', { name: 'phone', type: 'string' }); await e.insert('users', [{ id: '2', name: 'Frank', age: 33, phone: '123' }]); await e.alterTable('users', 'DROP', { name: 'phone', type: 'string' }); const rows = await e.find('users', { table: 'users' }); for (const row of rows) { expect('phone' in row).toBe(false); } await e.close(); }); test('ALTER 持久化:重启后 schema 与行一致', async () => { const name = `alt2-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; let e = new AriaEngine({ storageBackend: 'opfs', walSyncMode: 'full' }); await e.open(name, 1); await e.createTable({ name: 'users', columns: { id: { type: 'string', primaryKey: true }, name: { type: 'string' } } }); await e.insert('users', [{ id: '1', name: 'Alice' }]); await e.alterTable('users', 'ADD', { name: 'phone', type: 'string' }); await e.insert('users', [{ id: '2', name: 'Frank', phone: '123' }]); await e.alterTable('users', 'DROP', { name: 'phone', type: 'string' }); await e.close(); e = new AriaEngine({ storageBackend: 'opfs', walSyncMode: 'full' }); await e.open(name, 1); const schema = await e.getTableSchema('users'); expect(Object.keys(schema!.columns)).not.toContain('phone'); const rows = await e.find('users', { table: 'users' }); for (const row of rows) expect('phone' in row).toBe(false); await e.close(); }); test('ALTER 模拟崩溃(不 close)重启:schema 与行一致', async () => { const name = `alt3-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; let e = new AriaEngine({ storageBackend: 'opfs', walSyncMode: 'full' }); await e.open(name, 1); await e.createTable({ name: 'users', columns: { id: { type: 'string', primaryKey: true }, name: { type: 'string' } } }); await e.insert('users', [{ id: '1', name: 'Alice' }]); await e.alterTable('users', 'ADD', { name: 'phone', type: 'string' }); await e.insert('users', [{ id: '2', name: 'Frank', phone: '123' }]); await e.alterTable('users', 'DROP', { name: 'phone', type: 'string' }); // 不 close,模拟崩溃 e = new AriaEngine({ storageBackend: 'opfs', walSyncMode: 'full' }); await e.open(name, 1); const schema = await e.getTableSchema('users'); expect(Object.keys(schema!.columns)).not.toContain('phone'); const rows = await e.find('users', { table: 'users' }); for (const row of rows) expect('phone' in row).toBe(false); await e.close(); }); });