Files
MetonaSqlark/tests/join.test.ts
T
thzxx e2a590c5b1 feat: metona-sqlark v0.1.12 — 前端TypeScript关系型数据库
- 4种存储引擎:Memory / IndexedDB / OPFS / Hybrid
- 完整SQL支持:SELECT/INSERT/UPDATE/DELETE/JOIN/GROUP BY/HAVING/DISTINCT
- Query Builder链式API + TypeScript泛型支持
- 聚合函数:COUNT/SUM/AVG/MIN/MAX
- 事务、插件系统(14 hooks)、发布订阅、数据迁移、导入导出
- React/Vue框架集成
- 264个测试用例,93.46%覆盖率
- 零运行时依赖
2026-07-26 15:00:01 +08:00

210 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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<string, unknown>[];
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<string, unknown>[];
// 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<string, unknown>[];
// 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<string, unknown>[];
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<string, unknown>[];
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<string, unknown>[];
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<string, unknown>[];
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<string, unknown>[];
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');
});
});
});