release: v0.4.1 — Aria 级联/ALTER/clearAll + 流式查询/派生表 + 正确性加固
CI / test (20.x) (push) Successful in 10m4s
CI / test (22.x) (push) Successful in 10m8s
CI / test (24.x) (push) Successful in 9m55s
CI / test (18.x) (push) Successful in 10m9s

新增:
- 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:
thzxx
2026-08-08 13:36:34 +08:00
parent 82ad8e93b9
commit a1e4f5071c
38 changed files with 13493 additions and 8756 deletions
+106 -20
View File
@@ -212,18 +212,37 @@ export class Parser {
columns.push(...this.parseColumnList());
}
// FROM
this.expect(TokenType.FROM);
const tableName = this.expectIdentifier('table name');
// 表别名(可选)
// FROMv0.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;
}
/** 解析字面量值 */