/** * v0.4.0 功能扩展测试 * - B-1: 流式查询 queryStream / findStream * - B-2: 多列 ON 哈希连接 * - B-3: FROM 子查询(派生表) * - B-4: COUNT(DISTINCT) + NULLS FIRST/LAST */ import { MetonaSqlark } from '../src/core'; import { AriaEngine } from '../src/engine/aria/index'; import { resetOPFSMock } from './helpers/storage-harness'; import { engineMethod } from './helpers/assertions'; beforeEach(() => { resetOPFSMock(); }); const uniqueName = (prefix: string): string => `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; describe('[v0.4.0] B-1: 流式查询', () => { test('Aria findStream 逐行回调 + limit 生效', async () => { const e = new AriaEngine({ storageBackend: 'memory', walSyncMode: 'full' }); await e.open('stream1', 1); await e.createTable({ name: 'logs', columns: { id: { type: 'string', primaryKey: true }, level: { type: 'string' } } }); const rows: Record[] = []; for (let i = 0; i < 5000; i++) rows.push({ id: `${i}`, level: i % 2 ? 'error' : 'info' }); await e.insert('logs', rows); let count = 0; let sum = 0; const n = await e.findStream('logs', { table: 'logs', where: { level: 'error' }, limit: 100 }, (r) => { count++; sum += Number(r.id); }); expect(n).toBe(100); expect(count).toBe(100); expect(sum).toBeGreaterThan(0); await e.close(); }); test('db.queryStream 端到端(WHERE + 投影)', async () => { const db = new MetonaSqlark({ name: uniqueName('qs'), mode: 'memory' }); await db.init(); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, age: { type: 'number' } }); await db.query("INSERT INTO users VALUES ('1', 20), ('2', 30), ('3', 40), ('4', 30)"); const seen: Record[] = []; const total = await db.queryStream('SELECT id FROM users WHERE age >= 30', (row) => seen.push(row)); expect(total).toBe(3); expect(seen.map((r) => r.id).sort()).toEqual(['2', '3', '4']); await db.close(); }); test('queryStream 对 async 回调回退物化(不吞 Promise)', async () => { const db = new MetonaSqlark({ name: uniqueName('qa'), mode: 'memory' }); await db.init(); await db.defineTable('t', { id: { type: 'string', primaryKey: true } }); await db.query("INSERT INTO t VALUES ('1'), ('2')"); const seen: string[] = []; const total = await db.queryStream('SELECT * FROM t', async (row) => { await new Promise((r) => setTimeout(r, 1)); seen.push(String(row.id)); }); expect(total).toBe(2); expect(seen.sort()).toEqual(['1', '2']); await db.close(); }); test('queryStream 不支持非 SELECT 语句', async () => { const db = new MetonaSqlark({ name: uniqueName('qi'), mode: 'memory' }); await db.init(); await expect(db.queryStream('INSERT INTO x VALUES (1)', () => {})).rejects.toThrow('only supports SELECT'); await db.close(); }); test('db.table().stream() 逐行回调', async () => { const db = new MetonaSqlark({ name: uniqueName('qt'), mode: 'memory' }); await db.init(); await db.defineTable('t', { id: { type: 'string', primaryKey: true }, v: { type: 'number' } }); await db.query("INSERT INTO t VALUES ('1', 10), ('2', 20), ('3', 30)"); const seen: number[] = []; const total = await db.table('t').stream((row) => seen.push(Number(row.v)), { where: { v: { $gte: 20 } } }); expect(total).toBe(2); expect(seen.sort((a, b) => a - b)).toEqual([20, 30]); await db.close(); }); }); describe('[v0.4.0] B-2: 多列 ON 哈希连接', () => { test('复合键 INNER JOIN 结果正确', async () => { const db = new MetonaSqlark({ name: uniqueName('hj'), mode: 'memory' }); await db.init(); await db.defineTable('a', { id: { type: 'string', primaryKey: true }, tenant: { type: 'string', index: true }, key: { type: 'string' }, }); await db.defineTable('b', { id: { type: 'string', primaryKey: true }, tenant: { type: 'string', index: true }, key: { type: 'string' }, val: { type: 'number' }, }); await db.query("INSERT INTO a VALUES ('a1', 't1', 'k1'), ('a2', 't2', 'k2'), ('a3', 't1', 'k9')"); await db.query("INSERT INTO b VALUES ('b1', 't1', 'k1', 100), ('b2', 't1', 'k2', 200), ('b3', 't2', 'k2', 300)"); // tenant + key 复合等值连接 const rows = await db.query( "SELECT a.id AS aid, b.val FROM a INNER JOIN b ON a.tenant = b.tenant AND a.key = b.key", ) as Record[]; expect(rows.length).toBe(2); const byAid = Object.fromEntries(rows.map((r) => [r.aid, r['b.val']])); expect(byAid['a1']).toBe(100); // t1/k1 匹配 b1 expect(byAid['a2']).toBe(300); // t2/k2 匹配 b3(t1/k2 的 b2 因 tenant 不同被排除) expect(byAid['a3']).toBeUndefined(); // t1/k9 无匹配 await db.close(); }); test('复合键 LEFT JOIN 无匹配行置 null', async () => { const db = new MetonaSqlark({ name: uniqueName('hl'), mode: 'memory' }); await db.init(); await db.defineTable('a', { id: { type: 'string', primaryKey: true }, k1: { type: 'string', index: true }, k2: { type: 'string' } }); await db.defineTable('b', { id: { type: 'string', primaryKey: true }, k1: { type: 'string', index: true }, k2: { type: 'string' }, v: { type: 'number' } }); await db.query("INSERT INTO a VALUES ('a1', 'x', 'y'), ('a2', 'x', 'z')"); await db.query("INSERT INTO b VALUES ('b1', 'x', 'y', 5)"); const rows = await db.query( "SELECT a.id AS aid, b.v FROM a LEFT JOIN b ON a.k1 = b.k1 AND a.k2 = b.k2", ) as Record[]; expect(rows.length).toBe(2); expect(rows.find((r) => r.aid === 'a1')?.['b.v']).toBe(5); expect(rows.find((r) => r.aid === 'a2')?.['b.v']).toBeNull(); await db.close(); }); }); describe('[v0.4.0] B-3: FROM 子查询(派生表)', () => { test('FROM (SELECT ...) 派生表 + WHERE + ORDER BY', async () => { const db = new MetonaSqlark({ name: uniqueName('sub'), mode: 'memory' }); await db.init(); await db.defineTable('emp', { id: { type: 'string', primaryKey: true }, dept: { type: 'string' }, salary: { type: 'number' }, }); await db.query("INSERT INTO emp VALUES ('1', 'eng', 100), ('2', 'eng', 300), ('3', 'ops', 200)"); const rows = await db.query( "SELECT dept, total FROM (SELECT dept, SUM(salary) AS total FROM emp GROUP BY dept) AS t WHERE total > 150 ORDER BY total DESC", ) as Record[]; expect(rows).toEqual([{ dept: 'eng', total: 400 }, { dept: 'ops', total: 200 }]); await db.close(); }); test('派生表无别名也可用', async () => { const db = new MetonaSqlark({ name: uniqueName('sub2'), mode: 'memory' }); await db.init(); await db.defineTable('t', { id: { type: 'string', primaryKey: true }, v: { type: 'number' } }); await db.query("INSERT INTO t VALUES ('1', 1), ('2', 2)"); const rows = await db.query("SELECT v FROM (SELECT * FROM t WHERE v > 1) WHERE v < 10") as Record[]; expect(rows).toEqual([{ v: 2 }]); await db.close(); }); }); describe('[v0.4.0] B-4: COUNT(DISTINCT) + NULLS FIRST/LAST', () => { test('COUNT(DISTINCT col)', async () => { const db = new MetonaSqlark({ name: uniqueName('cd'), mode: 'memory' }); await db.init(); await db.defineTable('t', { id: { type: 'string', primaryKey: true }, dept: { type: 'string' } }); await db.query("INSERT INTO t VALUES ('1', 'a'), ('2', 'b'), ('3', 'a'), ('4', 'c')"); const rows = await db.query('SELECT COUNT(DISTINCT dept) AS n FROM t') as Record[]; expect(rows[0].n).toBe(3); await db.close(); }); test('SUM(DISTINCT col) 与 GROUP BY 组合', async () => { const db = new MetonaSqlark({ name: uniqueName('sd'), mode: 'memory' }); await db.init(); await db.defineTable('t', { id: { type: 'string', primaryKey: true }, g: { type: 'string' }, v: { type: 'number' } }); await db.query("INSERT INTO t VALUES ('1', 'x', 10), ('2', 'x', 10), ('3', 'y', 20)"); const rows = await db.query('SELECT g, SUM(DISTINCT v) AS s FROM t GROUP BY g ORDER BY g') as Record[]; expect(rows).toEqual([{ g: 'x', s: 10 }, { g: 'y', s: 20 }]); await db.close(); }); test('NULLS FIRST 排序', async () => { const db = new MetonaSqlark({ name: uniqueName('nf'), mode: 'memory' }); await db.init(); await db.defineTable('t', { id: { type: 'string', primaryKey: true }, v: { type: 'number' } }); await db.query("INSERT INTO t VALUES ('1', 10), ('2', NULL), ('3', 20)"); const rows = await db.query('SELECT v FROM t ORDER BY v ASC NULLS FIRST') as Record[]; expect(rows).toEqual([{ v: null }, { v: 10 }, { v: 20 }]); await db.close(); }); test('NULLS LAST 排序(降序时 NULL 排最后)', async () => { const db = new MetonaSqlark({ name: uniqueName('nl'), mode: 'memory' }); await db.init(); await db.defineTable('t', { id: { type: 'string', primaryKey: true }, v: { type: 'number' } }); await db.query("INSERT INTO t VALUES ('1', 10), ('2', NULL), ('3', 20)"); const rows = await db.query('SELECT v FROM t ORDER BY v DESC NULLS LAST') as Record[]; expect(rows).toEqual([{ v: 20 }, { v: 10 }, { v: null }]); await db.close(); }); test('字符串常量列 + \'\' 转义(无 FROM 查询)', async () => { const db = new MetonaSqlark({ name: uniqueName('lit'), mode: 'memory' }); await db.init(); const rows = await db.query("SELECT 'it''s a test' AS escaped") as Record[]; expect(rows).toEqual([{ escaped: "it's a test" }]); const mixed = await db.query("SELECT 'hello' AS greeting, 'world' AS subject") as Record[]; expect(mixed).toEqual([{ greeting: 'hello', subject: 'world' }]); await db.close(); }); test('ORDER BY / GROUP BY 支持表前缀列(u.name / o.amount)', async () => { const db = new MetonaSqlark({ name: uniqueName('prefix'), mode: 'memory' }); await db.init(); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, name: { type: 'string' }, age: { type: 'number' } }); await db.defineTable('orders', { id: { type: 'string', primaryKey: true }, user_id: { type: 'string' }, amount: { type: 'number' } }); await db.query("INSERT INTO users VALUES ('1', 'Alice', 30), ('2', 'Bob', 25)"); await db.query("INSERT INTO orders VALUES ('o1', '1', 1200), ('o2', '2', 50)"); // JOIN + ORDER BY 表前缀(演示页报错场景) const join = await db.query( 'SELECT u.name, o.amount FROM users u INNER JOIN orders o ON u.id = o.user_id ORDER BY o.amount DESC', ) as Record[]; expect(join.map((r) => r['u.name'])).toEqual(['Alice', 'Bob']); // JOIN + GROUP BY 表前缀 const grouped = await db.query( 'SELECT u.name, SUM(o.amount) AS total FROM users u INNER JOIN orders o ON u.id = o.user_id GROUP BY u.name', ) as Record[]; expect(grouped.map((r) => r.total).sort((a, b) => Number(b) - Number(a))).toEqual([1200, 50]); // 非 JOIN + ORDER BY / GROUP BY 表前缀 const plain = await db.query('SELECT name FROM users ORDER BY users.age DESC') as Record[]; expect(plain.map((r) => r.name)).toEqual(['Alice', 'Bob']); const byAge = await db.query('SELECT users.age, COUNT(*) AS n FROM users GROUP BY users.age') as Record[]; expect(byAge.length).toBe(2); await db.close(); }); test('INSERT ... SELECT 按源表列顺序映射(缺列不填,不报类型错)', async () => { const db = new MetonaSqlark({ name: uniqueName('insel'), mode: 'memory' }); await db.init(); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, name: { type: 'string', required: true }, email: { type: 'string' }, age: { type: 'number', default: 0 } }); await db.table('users').insertMany([{ id: '1', name: 'Alice', email: 'a@x.com', age: 30 }]); // alter 预设:ADD 列 → 显式列名插入(无 email)→ DROP 列,产生行键缺 email 的行 await db.query('ALTER TABLE users ADD COLUMN phone STRING'); await db.query("INSERT INTO users (id, name, age, phone) VALUES ('6', 'Frank', 33, '123')"); await db.query('ALTER TABLE users DROP COLUMN phone'); await db.query('CREATE TABLE users_backup (id STRING PRIMARY KEY, name STRING, email STRING, age NUMBER)'); await db.query('INSERT INTO users_backup SELECT * FROM users'); const rows = await db.query('SELECT * FROM users_backup ORDER BY id') as Record[]; expect(rows).toHaveLength(2); expect(rows[0]).toEqual({ id: '1', name: 'Alice', email: 'a@x.com', age: 30 }); expect(rows[1].id).toBe('6'); expect(rows[1].name).toBe('Frank'); expect(rows[1].age).toBe(33); // SELECT 指定列映射 await db.query('CREATE TABLE names_only (id STRING PRIMARY KEY, name STRING)'); await db.query('INSERT INTO names_only SELECT id, name FROM users'); const names = await db.query('SELECT * FROM names_only') as Record[]; expect(names.map((r) => r.name).sort()).toEqual(['Alice', 'Frank']); await db.close(); }); test('关联 EXISTS / NOT EXISTS(SELECT 列仅含 name 时绑定主键仍生效)', async () => { const db = new MetonaSqlark({ name: uniqueName('exists'), mode: 'memory' }); await db.init(); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, name: { type: 'string' }, age: { type: 'number' } }); await db.defineTable('orders', { id: { type: 'string', primaryKey: true }, user_id: { type: 'string' }, amount: { type: 'number' } }); await db.query("INSERT INTO users VALUES ('1', 'Alice', 30), ('2', 'Bob', 25), ('3', 'Eve', 22)"); await db.query("INSERT INTO orders VALUES ('o1', '1', 100), ('o2', '1', 200), ('o3', '2', 50)"); // SELECT 只投影 name(不含 id),EXISTS 绑定 u.id 必须仍工作 const withOrders = await db.query( 'SELECT u.name FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)', ) as Record[]; expect(withOrders.map((r) => r.name).sort()).toEqual(['Alice', 'Bob']); const without = await db.query( 'SELECT u.name FROM users u WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)', ) as Record[]; expect(without.map((r) => r.name)).toEqual(['Eve']); // SELECT 列带表前缀 + EXISTS 组合 const prefixed = await db.query( 'SELECT u.name FROM users u WHERE u.age > 21 AND EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)', ) as Record[]; expect(prefixed.map((r) => r.name).sort()).toEqual(['Alice', 'Bob']); await db.close(); }); test('HAVING 引用聚合表达式 + 标量子查询', async () => { const db = new MetonaSqlark({ name: uniqueName('having'), mode: 'memory' }); await db.init(); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, name: { type: 'string' } }); await db.defineTable('orders', { id: { type: 'string', primaryKey: true }, user_id: { type: 'string' }, amount: { type: 'number' } }); await db.query("INSERT INTO users VALUES ('1', 'Alice'), ('2', 'Bob'), ('3', 'Charlie')"); await db.query("INSERT INTO orders VALUES ('o1', '1', 1200), ('o2', '1', 50), ('o3', '2', 150), ('o4', '3', 400), ('o5', '3', 20)"); // HAVING SUM(...) > (SELECT AVG(...)):均值 364,Alice 1250 / Charlie 420 达标 const rows = await db.query( 'SELECT u.name, SUM(o.amount) AS spent FROM users u INNER JOIN orders o ON u.id = o.user_id GROUP BY u.name HAVING SUM(o.amount) > (SELECT AVG(amount) FROM orders)', ) as Record[]; expect(rows.map((r) => [r['u.name'], r.spent])).toEqual([['Alice', 1250], ['Charlie', 420]]); // HAVING 引用别名也生效 const byAlias = await db.query( 'SELECT u.name, SUM(o.amount) AS spent FROM users u INNER JOIN orders o ON u.id = o.user_id GROUP BY u.name HAVING spent > 400', ) as Record[]; expect(byAlias.map((r) => r['u.name']).sort()).toEqual(['Alice', 'Charlie']); await db.close(); }); test('JOIN 主表 WHERE 条件下推走索引 + DROP INDEX 报错', async () => { const db = new MetonaSqlark({ name: uniqueName('idx'), mode: 'memory' }); await db.init(); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, name: { type: 'string' } }); await db.defineTable('orders', { id: { type: 'string', primaryKey: true }, user_id: { type: 'string' }, product: { type: 'string' } }); await db.query("INSERT INTO users VALUES ('1', 'Alice'), ('2', 'Bob')"); await db.query("INSERT INTO orders VALUES ('o1', '1', 'Laptop'), ('o2', '1', 'Mouse'), ('o3', '2', 'Keyboard')"); await db.query('CREATE INDEX idx_orders_user ON orders (user_id)'); // WHERE 主表前缀条件下推:引擎收到的 orders 查询 where 为 { user_id: { $eq: '1' } } const eng = db.getEngine() as { find?: (...a: unknown[]) => Promise }; if (!eng.find) throw new Error('engine.find is required for this probe'); const engineFind = eng.find; const origFind = engineFind.bind(eng) as (...a: unknown[]) => Promise; let pushed: unknown = null; eng.find = async (...args: unknown[]): Promise => { const [t, q] = args as [string, { where?: unknown } | undefined]; if (t === 'orders' && q?.where) pushed = q.where; return origFind(...args); }; const rows = await db.query( "SELECT u.name, o.product FROM orders o JOIN users u ON u.id = o.user_id WHERE o.user_id = '1'", ) as Record[]; expect(rows.map((r) => [r['u.name'], r['o.product']])).toEqual([['Alice', 'Laptop'], ['Alice', 'Mouse']]); expect(pushed).toEqual({ user_id: { $eq: '1' } }); // DROP 存在的索引成功;再次 DROP(列已无索引)报 INDEX_NOT_FOUND await db.query('DROP INDEX idx_orders_user ON orders (user_id)'); await expect(db.query('DROP INDEX idx_nonexist ON orders (user_id)')).rejects.toThrow('Index on column'); // DROP 后回退全表扫描,结果不变 const after = await db.query("SELECT product FROM orders WHERE user_id = '2'") as Record[]; expect(after.map((r) => r.product)).toEqual(['Keyboard']); await db.close(); }); test('裸布尔列条件:WHERE done / CASE WHEN done(真值判断)', async () => { const db = new MetonaSqlark({ name: uniqueName('bool'), mode: 'memory' }); await db.init(); await db.defineTable('tasks', { id: { type: 'string', primaryKey: true }, title: { type: 'string' }, done: { type: 'boolean', default: false } }); await db.query("INSERT INTO tasks VALUES ('t1', 'A', false), ('t2', 'B', true), ('t3', 'C', false)"); // WHERE 裸列 const done = await db.query('SELECT id FROM tasks WHERE done') as Record[]; expect(done.map((r) => r.id)).toEqual(['t2']); // CASE WHEN 裸列(演示页 aria 预设) const labeled = await db.query( "SELECT title, CASE WHEN done THEN 'done' ELSE 'pending' END AS status FROM tasks", ) as Record[]; expect(labeled.find((r) => r.title === 'B')?.status).toBe('done'); expect(labeled.find((r) => r.title === 'A')?.status).toBe('pending'); await db.close(); }); test('Aria $in 查询去重(IN 子查询含重复值不返回重复行)', async () => { const db = new MetonaSqlark({ name: uniqueName('indup'), mode: 'aria', diskEngine: 'opfs' }); await db.init(); await engineMethod<() => Promise>(db.getEngine(), 'clearAll')(); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, name: { type: 'string' } }); await db.defineTable('orders', { id: { type: 'string', primaryKey: true }, user_id: { type: 'string' } }); await db.query("INSERT INTO users VALUES ('1', 'Alice'), ('2', 'Bob')"); await db.query("INSERT INTO orders VALUES ('o1', '1'), ('o2', '1')"); const rows = await db.query('SELECT name FROM users WHERE id IN (SELECT user_id FROM orders)') as Record[]; expect(rows).toHaveLength(1); expect(rows[0].name).toBe('Alice'); await db.close(); }); });