/** * v0.1.13 外键级联测试 * 覆盖 ON DELETE CASCADE / SET NULL / RESTRICT */ import { MetonaSqlark } from '../src/core'; import { parse } from '../src/sql/parser'; // =================================================================== // SQL Parser 外键语法 // =================================================================== describe('v0.1.13 外键级联 — Parser', () => { it('REFERENCES 语法解析', () => { const stmt = parse(`CREATE TABLE orders ( id STRING PRIMARY KEY, user_id STRING REFERENCES users(id) )`); expect(stmt.type).toBe('CREATE_TABLE'); if (stmt.type === 'CREATE_TABLE') { const col = stmt.columns.find((c) => c.name === 'user_id'); expect(col).toBeTruthy(); expect(col!.references).toBe('users.id'); } }); it('REFERENCES + ON DELETE CASCADE', () => { const stmt = parse(`CREATE TABLE orders ( id STRING PRIMARY KEY, user_id STRING REFERENCES users(id) ON DELETE CASCADE )`); if (stmt.type === 'CREATE_TABLE') { const col = stmt.columns.find((c) => c.name === 'user_id'); expect(col!.onDelete).toBe('CASCADE'); } }); it('REFERENCES + ON DELETE SET NULL', () => { const stmt = parse(`CREATE TABLE orders ( id STRING PRIMARY KEY, user_id STRING REFERENCES users(id) ON DELETE SET NULL )`); if (stmt.type === 'CREATE_TABLE') { const col = stmt.columns.find((c) => c.name === 'user_id'); expect(col!.onDelete).toBe('SET NULL'); } }); it('REFERENCES + ON UPDATE CASCADE', () => { const stmt = parse(`CREATE TABLE orders ( id STRING PRIMARY KEY, user_id STRING REFERENCES users(id) ON UPDATE CASCADE )`); if (stmt.type === 'CREATE_TABLE') { const col = stmt.columns.find((c) => c.name === 'user_id'); expect(col!.onUpdate).toBe('CASCADE'); } }); it('REFERENCES + 同时 ON DELETE 和 ON UPDATE', () => { const stmt = parse(`CREATE TABLE orders ( id STRING PRIMARY KEY, user_id STRING REFERENCES users(id) ON DELETE CASCADE ON UPDATE RESTRICT )`); if (stmt.type === 'CREATE_TABLE') { const col = stmt.columns.find((c) => c.name === 'user_id'); expect(col!.onDelete).toBe('CASCADE'); expect(col!.onUpdate).toBe('RESTRICT'); } }); }); // =================================================================== // ON DELETE CASCADE 执行 // =================================================================== describe('v0.1.13 外键级联 — ON DELETE CASCADE', () => { let db: MetonaSqlark; beforeEach(async () => { db = new MetonaSqlark({ name: 'fk-cascade-test', mode: 'memory' }); await db.init(); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, name: { type: 'string', required: true }, }); await db.defineTable('orders', { id: { type: 'string', primaryKey: true }, user_id: { type: 'string', references: 'users.id', onDelete: 'CASCADE' }, amount: { type: 'number' }, }); await db.table('users').insertMany([ { id: '1', name: 'Alice' }, { id: '2', name: 'Bob' }, ]); await db.table('orders').insertMany([ { id: 'o1', user_id: '1', amount: 100 }, { id: 'o2', user_id: '1', amount: 200 }, { id: 'o3', user_id: '2', amount: 50 }, ]); }); afterEach(async () => { await db.close(); }); it('删除用户 → 级联删除其订单', async () => { // 删除 Alice await db.table('users').delete().where({ id: '1' }).execute(); // Alice 被删除 expect(await db.table('users').count()).toBe(1); // Alice 的订单也被删除 const orders = await db.table('orders').select().execute(); expect(orders).toHaveLength(1); expect(orders[0].user_id).toBe('2'); }); it('删除没有订单的用户 → 不会级联删除', async () => { // 先查总数 const userCount = await db.table('users').count(); const orderCount = await db.table('orders').count(); // 删除一个没有任何订单的用户(创建临时用户) await db.table('users').insert({ id: '3', name: 'Eve' }); await db.table('users').delete().where({ id: '3' }).execute(); // 订单应该不变 expect(await db.table('orders').count()).toBe(orderCount); }); it('SQL DELETE 触发级联', async () => { await db.query("DELETE FROM users WHERE id = '2'"); const orders = await db.table('orders').select().execute(); expect(orders).toHaveLength(2); // 只剩 Alice 的 2 个订单 for (const o of orders) { expect(o.user_id).toBe('1'); } }); }); // =================================================================== // ON DELETE SET NULL // =================================================================== describe('v0.1.13 外键级联 — ON DELETE SET NULL', () => { let db: MetonaSqlark; beforeEach(async () => { db = new MetonaSqlark({ name: 'fk-setnull-test', mode: 'memory' }); await db.init(); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, name: { type: 'string', required: true }, }); await db.defineTable('orders', { id: { type: 'string', primaryKey: true }, user_id: { type: 'string', references: 'users.id', onDelete: 'SET NULL' }, amount: { type: 'number' }, }); await db.table('users').insert({ id: '1', name: 'Alice' }); await db.table('orders').insert({ id: 'o1', user_id: '1', amount: 100 }); }); afterEach(async () => { await db.close(); }); it('删除用户 → 订单 user_id 设为 NULL', async () => { await db.table('users').delete().where({ id: '1' }).execute(); const orders = await db.table('orders').select().execute(); expect(orders).toHaveLength(1); expect(orders[0].user_id).toBeNull(); }); }); // =================================================================== // ON DELETE RESTRICT // =================================================================== describe('v0.1.13 外键级联 — ON DELETE RESTRICT', () => { let db: MetonaSqlark; beforeEach(async () => { db = new MetonaSqlark({ name: 'fk-restrict-test', mode: 'memory' }); await db.init(); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, name: { type: 'string', required: true }, }); await db.defineTable('orders', { id: { type: 'string', primaryKey: true }, user_id: { type: 'string', references: 'users.id', onDelete: 'RESTRICT' }, amount: { type: 'number' }, }); await db.table('users').insert({ id: '1', name: 'Alice' }); await db.table('orders').insert({ id: 'o1', user_id: '1', amount: 100 }); }); afterEach(async () => { await db.close(); }); it('RESTRICT 模式下删除用户 → 不级联(保留孤儿订单)', async () => { await db.table('users').delete().where({ id: '1' }).execute(); // 订单仍然存在 const orders = await db.table('orders').select().execute(); expect(orders).toHaveLength(1); expect(orders[0].user_id).toBe('1'); }); it('无 references 的列不受影响', async () => { await db.table('users').delete().where({ id: '1' }).execute(); expect(await db.table('orders').count()).toBe(1); }); });