release: v0.4.1 — Aria 级联/ALTER/clearAll + 流式查询/派生表 + 正确性加固
新增: - AriaEngine 外键级联(CASCADE/SET NULL/RESTRICT)+ clearAll() 重置 API - 引擎级 alterTable:Aria DROP COLUMN 重写存储行 + schema 持久化 - 流式查询 queryStream / findStream(LSM 惰性扫描不物化) - FROM 派生表 / 多列 ON 哈希连接 / COUNT(DISTINCT) / NULLS FIRST/LAST - 普通列别名 + ORDER BY 别名 + 无表查询 + 字符串常量列 - 演示页引擎切换器(Memory/Aria)+ 预设自动重置 修复: - Aria WAL DROP_TABLE 崩溃恢复(删表复活)+ 恢复后 WAL 截断 - Memory update/delete 索引维护(unique 约束绕过) - 关联 EXISTS 绑定失效 / HAVING 标量子查询 / INSERT SELECT 位置错位 - 裸布尔列条件(WHERE done / CASE WHEN done) - Aria $in 重复行 / JOIN 主表 WHERE 下推 / DROP INDEX 报错 - ORDER BY/GROUP BY/SELECT 表前缀列 + SQL '' 标准转义 质量:894 测试 · 47 套件 · 81.5% 覆盖率
This commit is contained in:
+106
-20
@@ -212,18 +212,37 @@ export class Parser {
|
||||
columns.push(...this.parseColumnList());
|
||||
}
|
||||
|
||||
// FROM
|
||||
this.expect(TokenType.FROM);
|
||||
const tableName = this.expectIdentifier('table name');
|
||||
|
||||
// 表别名(可选)
|
||||
// FROM(v0.4.0 可选:SELECT 1 / SELECT 'lit' 无表查询)
|
||||
let fromSubquery: SelectStatement | SelectUnionStatement | undefined;
|
||||
let tableName = '';
|
||||
let alias: string | undefined;
|
||||
if (this.curTokenIs(TokenType.AS)) {
|
||||
this.nextToken();
|
||||
alias = this.expectIdentifier('alias');
|
||||
} else if (this.curToken.type === TokenType.IDENTIFIER && !this._isReservedAfterFrom()) {
|
||||
alias = this.curToken.value;
|
||||
if (this.curTokenIs(TokenType.FROM)) {
|
||||
this.nextToken();
|
||||
|
||||
// v0.4.0: FROM (SELECT ...) AS alias 派生表
|
||||
if (this.curTokenIs(TokenType.LPAREN)) {
|
||||
this.nextToken();
|
||||
fromSubquery = this.parseSelect();
|
||||
this.expect(TokenType.RPAREN);
|
||||
if (this.curTokenIs(TokenType.AS)) {
|
||||
this.nextToken();
|
||||
alias = this.expectIdentifier('alias');
|
||||
} else if (this.curToken.type === TokenType.IDENTIFIER && !this._isReservedAfterFrom()) {
|
||||
alias = this.curToken.value;
|
||||
this.nextToken();
|
||||
}
|
||||
} else {
|
||||
tableName = this.expectIdentifier('table name');
|
||||
|
||||
// 表别名(可选)
|
||||
if (this.curTokenIs(TokenType.AS)) {
|
||||
this.nextToken();
|
||||
alias = this.expectIdentifier('alias');
|
||||
} else if (this.curToken.type === TokenType.IDENTIFIER && !this._isReservedAfterFrom()) {
|
||||
alias = this.curToken.value;
|
||||
this.nextToken();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const stmt: SelectStatement = {
|
||||
@@ -234,6 +253,9 @@ export class Parser {
|
||||
alias,
|
||||
where: {},
|
||||
};
|
||||
if (fromSubquery) {
|
||||
stmt.fromSubquery = fromSubquery;
|
||||
}
|
||||
|
||||
// JOIN 子句(可选,支持多个)
|
||||
const joins = this.parseJoinClauses();
|
||||
@@ -824,6 +846,17 @@ export class Parser {
|
||||
return result;
|
||||
}
|
||||
|
||||
// v0.4.1: 裸布尔列条件(WHERE done / CASE WHEN done THEN)— 列后直接是终止符时视为真值判断
|
||||
if (
|
||||
this.curTokenIs(TokenType.AND) || this.curTokenIs(TokenType.OR) ||
|
||||
this.curTokenIs(TokenType.RPAREN) || this.curTokenIs(TokenType.EOF) ||
|
||||
(this.curToken.type === TokenType.IDENTIFIER && ['THEN', 'END', 'ELSE', 'NULLS', 'LIMIT', 'OFFSET', 'ORDER', 'GROUP', 'HAVING', 'UNION', 'WHERE'].includes(this.curToken.value.toUpperCase()))
|
||||
) {
|
||||
const result: WhereCondition = {};
|
||||
result[column] = { $eq: true };
|
||||
return result;
|
||||
}
|
||||
|
||||
// 比较运算符
|
||||
const op = this.parseComparisonOp();
|
||||
|
||||
@@ -897,15 +930,30 @@ export class Parser {
|
||||
|
||||
private parseColumnList(): string[] {
|
||||
const cols: string[] = [];
|
||||
cols.push(this.parseColumnRef());
|
||||
cols.push(this.parseColumnWithAlias());
|
||||
while (this.curTokenIs(TokenType.COMMA)) {
|
||||
this.nextToken();
|
||||
cols.push(this.parseColumnRef());
|
||||
cols.push(this.parseColumnWithAlias());
|
||||
}
|
||||
return cols;
|
||||
}
|
||||
|
||||
/** 解析列引用:支持 'col'、'table.col'、'COUNT(*)'/'SUM(col)'、数字常量列(SELECT 1)和 CASE WHEN 表达式(v0.3.1) */
|
||||
/** v0.3.3: 解析列(支持 `col AS alias` 显式别名与 `col alias` 隐式别名) */
|
||||
private parseColumnWithAlias(): string {
|
||||
let col = this.parseColumnRef();
|
||||
if (this.curTokenIs(TokenType.AS)) {
|
||||
this.nextToken();
|
||||
const alias = this.expectIdentifier('alias');
|
||||
col = `${col} AS ${alias}`;
|
||||
} else if (this.curToken.type === TokenType.IDENTIFIER && !this._isReservedAfterFrom() && !this._isJoinKeyword()) {
|
||||
const alias = this.curToken.value;
|
||||
this.nextToken();
|
||||
col = `${col} AS ${alias}`;
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
/** 解析列引用:支持 'col'、'table.col'、'COUNT(*)'/'SUM(col)'、数字常量列(SELECT 1)、字符串常量列(SELECT 'x',v0.4.0)和 CASE WHEN 表达式(v0.3.1) */
|
||||
private parseColumnRef(): string {
|
||||
// CASE WHEN 表达式(v0.3.1)
|
||||
if (this.curTokenIs(TokenType.CASE)) {
|
||||
@@ -919,6 +967,13 @@ export class Parser {
|
||||
return value;
|
||||
}
|
||||
|
||||
// v0.4.0: 字符串常量列:SELECT 'value' FROM t
|
||||
if (this.curTokenIs(TokenType.STRING)) {
|
||||
const value = this.curToken.value;
|
||||
this.nextToken();
|
||||
return `'${value}'`;
|
||||
}
|
||||
|
||||
// 聚合函数?
|
||||
if (
|
||||
this.curTokenIs(TokenType.COUNT) ||
|
||||
@@ -973,11 +1028,19 @@ export class Parser {
|
||||
return text;
|
||||
}
|
||||
|
||||
/** 解析聚合函数调用: COUNT(*), SUM(col), AVG(col), MIN(col), MAX(col) */ private parseAggregateCall(): string {
|
||||
/** 解析聚合函数调用: COUNT(*), SUM(col), AVG(col), MIN(col), MAX(col),v0.4.0 支持 COUNT(DISTINCT col) */
|
||||
private parseAggregateCall(): string {
|
||||
const func = this.curToken.value.toUpperCase();
|
||||
this.nextToken();
|
||||
this.expect(TokenType.LPAREN);
|
||||
|
||||
// v0.4.0: COUNT(DISTINCT col) 等去重聚合
|
||||
let distinct = false;
|
||||
if (this.curTokenIs(TokenType.DISTINCT)) {
|
||||
distinct = true;
|
||||
this.nextToken();
|
||||
}
|
||||
|
||||
let arg: string;
|
||||
if (this.curTokenIs(TokenType.STAR)) {
|
||||
arg = '*';
|
||||
@@ -998,10 +1061,11 @@ export class Parser {
|
||||
this.nextToken();
|
||||
}
|
||||
|
||||
const inner = distinct ? `DISTINCT ${arg}` : arg;
|
||||
if (alias) {
|
||||
return `${func}(${arg}) AS ${alias}`;
|
||||
return `${func}(${inner}) AS ${alias}`;
|
||||
}
|
||||
return `${func}(${arg})`;
|
||||
return `${func}(${inner})`;
|
||||
}
|
||||
|
||||
private _isAggregateAlias(): boolean {
|
||||
@@ -1010,10 +1074,10 @@ export class Parser {
|
||||
|
||||
private parseIdentifierList(): string[] {
|
||||
const ids: string[] = [];
|
||||
ids.push(this.expectIdentifier('identifier'));
|
||||
ids.push(this.parseIdentifierWithDot());
|
||||
while (this.curTokenIs(TokenType.COMMA)) {
|
||||
this.nextToken();
|
||||
ids.push(this.expectIdentifier('identifier'));
|
||||
ids.push(this.parseIdentifierWithDot());
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
@@ -1039,7 +1103,7 @@ export class Parser {
|
||||
}
|
||||
|
||||
private parseOrderBy(): OrderBy {
|
||||
const column = this.expectIdentifier('column name');
|
||||
const column = this.parseIdentifierWithDot();
|
||||
let direction: SortDirection = 'asc';
|
||||
if (this.curTokenIs(TokenType.ASC)) {
|
||||
this.nextToken();
|
||||
@@ -1047,7 +1111,29 @@ export class Parser {
|
||||
direction = 'desc';
|
||||
this.nextToken();
|
||||
}
|
||||
return { column, direction };
|
||||
// v0.4.0: NULLS FIRST / NULLS LAST
|
||||
let nulls: 'first' | 'last' | undefined;
|
||||
if (this.curTokenIs(TokenType.IDENTIFIER) && this.curToken.value.toUpperCase() === 'NULLS') {
|
||||
this.nextToken();
|
||||
if (this.curTokenIs(TokenType.IDENTIFIER) && this.curToken.value.toUpperCase() === 'FIRST') {
|
||||
nulls = 'first';
|
||||
this.nextToken();
|
||||
} else if (this.curTokenIs(TokenType.IDENTIFIER) && this.curToken.value.toUpperCase() === 'LAST') {
|
||||
nulls = 'last';
|
||||
this.nextToken();
|
||||
}
|
||||
}
|
||||
return { column, direction, ...(nulls ? { nulls } : {}) };
|
||||
}
|
||||
|
||||
/** v0.4.0: 标识符(支持 'table.column' 带表前缀引用,用于 ORDER BY / GROUP BY) */
|
||||
private parseIdentifierWithDot(): string {
|
||||
const first = this.expectIdentifier('identifier');
|
||||
if (this.curTokenIs(TokenType.DOT)) {
|
||||
this.nextToken();
|
||||
return `${first}.${this.expectIdentifier('identifier')}`;
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
/** 解析字面量值 */
|
||||
|
||||
Reference in New Issue
Block a user