/** * JOIN 功能测试 (v0.1.1) */ import { MetonaSqlark } from '../src/core'; describe('JOIN (v0.1.1)', () => { let db: MetonaSqlark; beforeEach(async () => { db = new MetonaSqlark({ name: 'test-join', mode: 'memory' }); await db.init(); // 创建测试表 await db.defineTable('users', { id: { type: 'string', primaryKey: true }, name: { type: 'string', required: true }, dept_id: { type: 'number' }, }); await db.defineTable('departments', { id: { type: 'number', primaryKey: true }, name: { type: 'string', required: true }, }); await db.defineTable('orders', { id: { type: 'string', primaryKey: true }, user_id: { type: 'string' }, amount: { type: 'number' }, }); // 插入测试数据 await db.table('users').insertMany([ { id: '1', name: 'Alice', dept_id: 1 }, { id: '2', name: 'Bob', dept_id: 1 }, { id: '3', name: 'Charlie', dept_id: 2 }, ]); await db.table('departments').insertMany([ { id: 1, name: 'Engineering' }, { id: 2, name: 'Sales' }, { id: 3, name: 'HR' }, // 无用户 ]); 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: 150 }, ]); }); afterEach(async () => { await db.close(); }); // =================================================================== // SQL JOIN // =================================================================== describe('SQL JOIN', () => { it('INNER JOIN', async () => { const result = await db.query( 'SELECT users.name, departments.name FROM users INNER JOIN departments ON users.dept_id = departments.id', ) as Record[]; expect(result).toHaveLength(3); }); it('LEFT JOIN', async () => { const result = await db.query( 'SELECT departments.name FROM departments LEFT JOIN users ON departments.id = users.dept_id', ) as Record[]; // HR 部门没有用户,LEFT JOIN 应保留 expect(result.length).toBeGreaterThanOrEqual(4); }); it('CROSS JOIN', async () => { const result = await db.query( 'SELECT * FROM users CROSS JOIN departments', ) as Record[]; // 3 users × 3 departments = 9 expect(result).toHaveLength(9); }); it('JOIN with alias', async () => { const result = await db.query( 'SELECT u.name, d.name FROM users u INNER JOIN departments d ON u.dept_id = d.id', ) as Record[]; expect(result).toHaveLength(3); }); it('JOIN with AS alias', async () => { const result = await db.query( 'SELECT u.name FROM users AS u INNER JOIN orders AS o ON u.id = o.user_id', ) as Record[]; expect(result.length).toBeGreaterThanOrEqual(3); }); it('JOIN with WHERE', async () => { const result = await db.query( 'SELECT u.name, o.amount FROM users u INNER JOIN orders o ON u.id = o.user_id WHERE o.amount > 120', ) as Record[]; expect(result).toHaveLength(2); // o2(200) + o3(150) }); it('Multiple JOIN', async () => { const result = await db.query( 'SELECT u.name, d.name, o.amount FROM users u INNER JOIN departments d ON u.dept_id = d.id INNER JOIN orders o ON u.id = o.user_id', ) as Record[]; expect(result.length).toBeGreaterThanOrEqual(3); }); it('LEFT JOIN with no match returns null', async () => { const result = await db.query( 'SELECT d.name, u.name FROM departments d LEFT JOIN users u ON d.id = u.dept_id WHERE d.id = 3', ) as Record[]; expect(result).toHaveLength(1); // HR 部门无用户,u.name 应为 null // Note: column projection may vary }); }); // =================================================================== // QueryBuilder JOIN // =================================================================== describe('QueryBuilder JOIN', () => { it('innerJoin via QueryBuilder', async () => { const results = await db.table('users') .select(['users.name', 'departments.name']) .innerJoin('departments', { 'users.dept_id': { $col: 'departments.id' } }) .execute(); expect(results).toHaveLength(3); }); it('leftJoin via QueryBuilder', async () => { const results = await db.table('departments') .select() .leftJoin('users', { 'departments.id': { $col: 'users.dept_id' } }) .execute(); expect(results.length).toBeGreaterThanOrEqual(4); }); it('crossJoin via QueryBuilder', async () => { const results = await db.table('users') .select() .crossJoin('departments') .execute(); expect(results).toHaveLength(9); }); it('join with where + orderBy + limit', async () => { const results = await db.table('users') .select() .innerJoin('orders', { 'users.id': { $col: 'orders.user_id' } }) .where({ 'orders.amount': { $gt: 100 } }) .orderBy('orders.amount', 'desc') .limit(2) .execute(); expect(results.length).toBeGreaterThanOrEqual(1); expect(results.length).toBeLessThanOrEqual(2); // 按 amount desc 排序 if (results.length === 2) { expect((results[0] as any)['orders.amount']).toBeGreaterThanOrEqual( (results[1] as any)['orders.amount'], ); } }); }); // =================================================================== // Parser JOIN 语法 // =================================================================== describe('Parser JOIN 语法', () => { it('INNER JOIN 解析', () => { const { parse } = require('../src/sql/parser'); const ast = parse('SELECT * FROM users INNER JOIN orders ON users.id = orders.user_id'); expect(ast.joins).toHaveLength(1); expect(ast.joins[0].type).toBe('INNER'); expect(ast.joins[0].table).toBe('orders'); }); it('LEFT OUTER JOIN 解析', () => { const { parse } = require('../src/sql/parser'); const ast = parse('SELECT * FROM users LEFT OUTER JOIN orders ON users.id = orders.user_id'); expect(ast.joins[0].type).toBe('LEFT'); }); it('RIGHT JOIN 解析', () => { const { parse } = require('../src/sql/parser'); const ast = parse('SELECT * FROM users RIGHT JOIN orders ON users.id = orders.user_id'); expect(ast.joins[0].type).toBe('RIGHT'); }); it('CROSS JOIN 解析', () => { const { parse } = require('../src/sql/parser'); const ast = parse('SELECT * FROM users CROSS JOIN departments'); expect(ast.joins[0].type).toBe('CROSS'); }); it('表别名解析', () => { const { parse } = require('../src/sql/parser'); const ast = parse('SELECT u.name FROM users u'); expect(ast.alias).toBe('u'); }); it('AS 别名解析', () => { const { parse } = require('../src/sql/parser'); const ast = parse('SELECT u.name FROM users AS u'); expect(ast.alias).toBe('u'); }); }); });