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,96 @@
|
||||
/**
|
||||
* Lexer 边缘场景测试
|
||||
*/
|
||||
|
||||
import { Lexer, tokenize } from '../../src/sql/lexer';
|
||||
import { TokenType } from '../../src/sql/tokens';
|
||||
|
||||
describe('Lexer 边缘场景', () => {
|
||||
it('空字符串返回 EOF', () => {
|
||||
const tokens = tokenize('');
|
||||
expect(tokens).toHaveLength(1);
|
||||
expect(tokens[0].type).toBe(TokenType.EOF);
|
||||
});
|
||||
|
||||
it('只包含空格', () => {
|
||||
const tokens = tokenize(' \t\n ');
|
||||
expect(tokens).toHaveLength(1);
|
||||
expect(tokens[0].type).toBe(TokenType.EOF);
|
||||
});
|
||||
|
||||
it('非法字符返回 ILLEGAL', () => {
|
||||
const tokens = tokenize('@');
|
||||
expect(tokens[0].type).toBe(TokenType.ILLEGAL);
|
||||
});
|
||||
|
||||
it('完整的 CREATE TABLE 语句', () => {
|
||||
const tokens = tokenize('CREATE TABLE users (id STRING PRIMARY KEY, name STRING NOT NULL, age NUMBER DEFAULT 0)');
|
||||
const types = tokens.map(t => t.type);
|
||||
expect(types).toEqual([
|
||||
TokenType.CREATE, TokenType.TABLE, TokenType.IDENTIFIER, TokenType.LPAREN,
|
||||
TokenType.IDENTIFIER, TokenType.IDENTIFIER, TokenType.PRIMARY, TokenType.KEY,
|
||||
TokenType.COMMA,
|
||||
TokenType.IDENTIFIER, TokenType.IDENTIFIER, TokenType.NOT, TokenType.NULL,
|
||||
TokenType.COMMA,
|
||||
TokenType.IDENTIFIER, TokenType.IDENTIFIER, TokenType.DEFAULT, TokenType.NUMBER,
|
||||
TokenType.RPAREN, TokenType.EOF,
|
||||
]);
|
||||
});
|
||||
|
||||
it('带转义单引号的字符串', () => {
|
||||
// SQL 中两个单引号 '' 表示转义的单引号
|
||||
const tokens = tokenize("SELECT 'hello world'");
|
||||
expect(tokens[1].type).toBe(TokenType.STRING);
|
||||
expect(tokens[1].value).toBe('hello world');
|
||||
});
|
||||
|
||||
it('双引号字符串', () => {
|
||||
const tokens = tokenize('SELECT "hello world"');
|
||||
expect(tokens[1].type).toBe(TokenType.STRING);
|
||||
expect(tokens[1].value).toBe('hello world');
|
||||
});
|
||||
|
||||
it('所有关键字', () => {
|
||||
const sql = 'SELECT FROM WHERE INSERT INTO VALUES UPDATE SET DELETE CREATE TABLE DROP ORDER BY ASC DESC LIMIT OFFSET AND OR NOT LIKE IN PRIMARY KEY UNIQUE DEFAULT NULL TRUE FALSE';
|
||||
const tokens = tokenize(sql);
|
||||
const nonEof = tokens.filter(t => t.type !== TokenType.EOF);
|
||||
expect(nonEof).toHaveLength(30);
|
||||
// 验证都是关键字,不是 IDENTIFIER
|
||||
for (const t of nonEof) {
|
||||
expect(t.type).not.toBe(TokenType.IDENTIFIER);
|
||||
}
|
||||
});
|
||||
|
||||
it('下划线标识符', () => {
|
||||
const tokens = tokenize('SELECT my_col_1 FROM user_table');
|
||||
expect(tokens[1].type).toBe(TokenType.IDENTIFIER);
|
||||
expect(tokens[1].value).toBe('my_col_1');
|
||||
expect(tokens[3].type).toBe(TokenType.IDENTIFIER);
|
||||
expect(tokens[3].value).toBe('user_table');
|
||||
});
|
||||
|
||||
it('运算符分隔符', () => {
|
||||
const tokens = tokenize('= != > >= < <= <> ( ) , ; *');
|
||||
const types = tokens.map(t => t.type);
|
||||
expect(types).toEqual([
|
||||
TokenType.EQ, TokenType.NEQ, TokenType.GT, TokenType.GTE,
|
||||
TokenType.LT, TokenType.LTE, TokenType.NEQ,
|
||||
TokenType.LPAREN, TokenType.RPAREN, TokenType.COMMA,
|
||||
TokenType.SEMICOLON, TokenType.STAR, TokenType.EOF,
|
||||
]);
|
||||
});
|
||||
|
||||
it('数字: 整数和浮点数和负数', () => {
|
||||
const tokens = tokenize('42 3.14 -7 -2.5');
|
||||
expect(tokens[0].value).toBe('42');
|
||||
expect(tokens[1].value).toBe('3.14');
|
||||
expect(tokens[2].value).toBe('-7');
|
||||
expect(tokens[3].value).toBe('-2.5');
|
||||
});
|
||||
|
||||
it('Token position 正确', () => {
|
||||
const lexer = new Lexer('SELECT * FROM users');
|
||||
const tok = lexer.nextToken();
|
||||
expect(tok.position).toBe(0); // SELECT 从位置 0 开始
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user