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%覆盖率 - 零运行时依赖
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* QueryBuilder 和 Compiler/Executor 测试
|
||||
*/
|
||||
|
||||
import { MemoryEngine } from '../../src/engine/memory';
|
||||
import { createSchema } from '../../src/table/schema';
|
||||
import { SelectQueryBuilder, UpdateQueryBuilder, DeleteQueryBuilder } from '../../src/query/builder';
|
||||
import { compileStatement } from '../../src/query/compiler';
|
||||
import { QueryExecutor } from '../../src/query/executor';
|
||||
import { parse } from '../../src/sql/parser';
|
||||
|
||||
describe('QueryBuilder', () => {
|
||||
let engine: MemoryEngine;
|
||||
|
||||
beforeEach(async () => {
|
||||
engine = new MemoryEngine();
|
||||
await engine.open('test-qb', 1);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await engine.close();
|
||||
});
|
||||
|
||||
// ---- SelectQueryBuilder ----
|
||||
|
||||
describe('SelectQueryBuilder', () => {
|
||||
it('基础查询', () => {
|
||||
const qb = new SelectQueryBuilder(engine, 'users');
|
||||
expect(qb.toAST()).toMatchObject({
|
||||
type: 'SELECT',
|
||||
columns: ['*'],
|
||||
from: 'users',
|
||||
where: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('链式调用', () => {
|
||||
const qb = new SelectQueryBuilder(engine, 'users', ['id', 'name']);
|
||||
qb.where({ age: { $gt: 18 } }).orderBy('name', 'asc').limit(10).offset(5);
|
||||
|
||||
const ast = qb.toAST();
|
||||
expect(ast.columns).toEqual(['id', 'name']);
|
||||
expect(ast.where).toEqual({ age: { $gt: 18 } });
|
||||
expect(ast.orderBy).toEqual([{ column: 'name', direction: 'asc' }]);
|
||||
expect(ast.limit).toBe(10);
|
||||
expect(ast.offset).toBe(5);
|
||||
});
|
||||
|
||||
it('多次 where 合并条件', () => {
|
||||
const qb = new SelectQueryBuilder(engine, 'users');
|
||||
qb.where({ age: { $gt: 18 } }).where({ active: true });
|
||||
expect(qb.toAST().where).toEqual({ age: { $gt: 18 }, active: true });
|
||||
});
|
||||
|
||||
it('多次 orderBy 追加排序', () => {
|
||||
const qb = new SelectQueryBuilder(engine, 'users');
|
||||
qb.orderBy('age', 'desc').orderBy('name', 'asc');
|
||||
expect(qb.toAST().orderBy).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
// ---- UpdateQueryBuilder ----
|
||||
|
||||
describe('UpdateQueryBuilder', () => {
|
||||
it('基础更新', () => {
|
||||
const qb = new UpdateQueryBuilder(engine, 'users', { age: 31 });
|
||||
qb.where({ id: '1' });
|
||||
const ast = qb.toAST();
|
||||
expect(ast).toMatchObject({
|
||||
type: 'UPDATE',
|
||||
table: 'users',
|
||||
sets: { age: 31 },
|
||||
where: { id: '1' },
|
||||
});
|
||||
});
|
||||
|
||||
it('不带 where', () => {
|
||||
const qb = new UpdateQueryBuilder(engine, 'users', { active: false });
|
||||
expect(qb.toAST().where).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
// ---- DeleteQueryBuilder ----
|
||||
|
||||
describe('DeleteQueryBuilder', () => {
|
||||
it('基础删除', () => {
|
||||
const qb = new DeleteQueryBuilder(engine, 'users');
|
||||
qb.where({ id: '1' });
|
||||
const ast = qb.toAST();
|
||||
expect(ast).toMatchObject({
|
||||
type: 'DELETE',
|
||||
from: 'users',
|
||||
where: { id: '1' },
|
||||
});
|
||||
});
|
||||
|
||||
it('不带 where', () => {
|
||||
const qb = new DeleteQueryBuilder(engine, 'users');
|
||||
expect(qb.toAST().where).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ===================================================================
|
||||
|
||||
describe('Compiler', () => {
|
||||
it('编译 SELECT', () => {
|
||||
const ast = parse('SELECT id, name FROM users WHERE age > 18 ORDER BY age DESC LIMIT 10');
|
||||
const plan = compileStatement(ast);
|
||||
expect(plan.table).toBe('users');
|
||||
expect(plan.columns).toEqual(['id', 'name']);
|
||||
expect(plan.where).toBeDefined();
|
||||
expect(plan.orderBy).toEqual([{ column: 'age', direction: 'desc' }]);
|
||||
expect(plan.limit).toBe(10);
|
||||
});
|
||||
|
||||
it('编译 DELETE', () => {
|
||||
const ast = parse("DELETE FROM users WHERE id = '1'");
|
||||
const plan = compileStatement(ast);
|
||||
expect(plan.table).toBe('users');
|
||||
expect(plan.where).toBeDefined();
|
||||
});
|
||||
|
||||
it('编译 UPDATE', () => {
|
||||
const ast = parse("UPDATE users SET age = 31 WHERE id = '1'");
|
||||
const plan = compileStatement(ast);
|
||||
expect(plan.table).toBe('users');
|
||||
expect(plan.where).toBeDefined();
|
||||
});
|
||||
|
||||
it('非查询语句抛出错误', () => {
|
||||
const ast = parse('CREATE TABLE users (id STRING PRIMARY KEY)');
|
||||
expect(() => compileStatement(ast)).toThrow('Cannot compile');
|
||||
});
|
||||
});
|
||||
|
||||
// ===================================================================
|
||||
|
||||
describe('QueryExecutor', () => {
|
||||
let engine: MemoryEngine;
|
||||
let executor: QueryExecutor;
|
||||
|
||||
beforeEach(async () => {
|
||||
engine = new MemoryEngine();
|
||||
await engine.open('test-exec', 1);
|
||||
executor = new QueryExecutor(engine);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await engine.close();
|
||||
});
|
||||
|
||||
it('执行 CREATE TABLE', async () => {
|
||||
const ast = parse('CREATE TABLE users (id STRING PRIMARY KEY, name STRING)');
|
||||
await executor.execute(ast);
|
||||
expect(await engine.hasTable('users')).toBe(true);
|
||||
});
|
||||
|
||||
it('执行 INSERT + SELECT', async () => {
|
||||
await executor.execute(parse('CREATE TABLE users (id STRING PRIMARY KEY, name STRING, age NUMBER)'));
|
||||
await executor.execute(parse("INSERT INTO users (id, name, age) VALUES ('1', 'Alice', 30)"));
|
||||
await executor.execute(parse("INSERT INTO users VALUES ('2', 'Bob', 25)"));
|
||||
|
||||
const result = await executor.execute(parse('SELECT * FROM users')) as Record<string, unknown>[];
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('执行 UPDATE', async () => {
|
||||
await executor.execute(parse('CREATE TABLE users (id STRING PRIMARY KEY, name STRING, age NUMBER)'));
|
||||
await executor.execute(parse("INSERT INTO users VALUES ('1', 'Alice', 30)"));
|
||||
await executor.execute(parse("UPDATE users SET age = 31 WHERE id = '1'"));
|
||||
|
||||
const result = await executor.execute(parse("SELECT * FROM users WHERE id = '1'")) as Record<string, unknown>[];
|
||||
expect(result[0].age).toBe(31);
|
||||
});
|
||||
|
||||
it('执行 DELETE', async () => {
|
||||
await executor.execute(parse('CREATE TABLE users (id STRING PRIMARY KEY, name STRING)'));
|
||||
await executor.execute(parse("INSERT INTO users VALUES ('1', 'Alice')"));
|
||||
await executor.execute(parse("INSERT INTO users VALUES ('2', 'Bob')"));
|
||||
await executor.execute(parse("DELETE FROM users WHERE id = '1'"));
|
||||
|
||||
const result = await executor.execute(parse('SELECT * FROM users')) as Record<string, unknown>[];
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('执行 DROP TABLE', async () => {
|
||||
await executor.execute(parse('CREATE TABLE users (id STRING PRIMARY KEY)'));
|
||||
await executor.execute(parse('DROP TABLE users'));
|
||||
expect(await engine.hasTable('users')).toBe(false);
|
||||
});
|
||||
|
||||
it('操作不存在的表抛出错误', async () => {
|
||||
await expect(
|
||||
executor.execute(parse('SELECT * FROM nonexistent')),
|
||||
).rejects.toThrow('does not exist');
|
||||
});
|
||||
|
||||
it('getEngine', () => {
|
||||
expect(executor.getEngine()).toBe(engine);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user