test: v0.1.13 新增42项测试覆盖(事务回滚/子查询/外键级联/连接池)
This commit is contained in:
+33
-1
@@ -46,15 +46,21 @@ export class QueryExecutor {
|
||||
}
|
||||
|
||||
const hasGroupBy = !!(stmt.groupBy && stmt.groupBy.length > 0);
|
||||
const hasAggregate = !hasGroupBy && this._hasAggregateColumn(stmt.columns);
|
||||
let rows: Record<string, unknown>[];
|
||||
|
||||
if (!stmt.joins || stmt.joins.length === 0) {
|
||||
const plan = compileStatement(hasGroupBy ? { ...stmt, columns: ['*'] } : stmt);
|
||||
const plan = compileStatement(hasGroupBy || hasAggregate ? { ...stmt, columns: ['*'] } : stmt);
|
||||
rows = await this.engine.find(plan.table, plan);
|
||||
} else {
|
||||
rows = await this.executeJoinSelect(stmt);
|
||||
}
|
||||
|
||||
// 无 GROUP BY 但有聚合 → 计算单行聚合结果
|
||||
if (hasAggregate) {
|
||||
rows = [this.computeSingleAggregate(rows, stmt)];
|
||||
}
|
||||
|
||||
if (hasGroupBy) rows = this.executeGroupBy(rows, stmt);
|
||||
if (stmt.distinct) rows = this.executeDistinct(rows);
|
||||
if (stmt.having && Object.keys(stmt.having).length > 0) {
|
||||
@@ -233,6 +239,32 @@ export class QueryExecutor {
|
||||
|
||||
getEngine(): IStorageEngine { return this.engine; }
|
||||
|
||||
// ===================================================================
|
||||
// 无 GROUP BY 时的聚合计算
|
||||
// ===================================================================
|
||||
|
||||
/** 检查 SELECT 列列表中是否包含聚合函数 */
|
||||
private _hasAggregateColumn(columns: string[]): boolean {
|
||||
return columns.some((col) => /^(COUNT|SUM|AVG|MIN|MAX)\(/i.test(col));
|
||||
}
|
||||
|
||||
/** 计算单行聚合结果(无 GROUP BY) */
|
||||
private computeSingleAggregate(rows: Record<string, unknown>[], stmt: SelectStatement): Record<string, unknown> {
|
||||
const result: Record<string, unknown> = {};
|
||||
for (const colExpr of stmt.columns) {
|
||||
if (colExpr === '*') continue;
|
||||
const m = colExpr.match(/^(COUNT|SUM|AVG|MIN|MAX)\((.+?)\)(?:\s+AS\s+(\w+))?$/i);
|
||||
if (m) {
|
||||
const [, func, arg, alias] = m;
|
||||
result[alias || colExpr] = this.computeAggregate(func.toUpperCase(), rows, arg.trim());
|
||||
} else {
|
||||
// 非聚合列取第一行的值
|
||||
result[colExpr] = rows.length > 0 ? rows[0][colExpr] : null;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// 子查询解析
|
||||
// ===================================================================
|
||||
|
||||
+37
-2
@@ -454,8 +454,8 @@ export class Parser {
|
||||
/** simple_cond → column op value | column IS [NOT] NULL | column [NOT] LIKE pattern
|
||||
* | column [NOT] IN (values) | NOT condition | (condition) */
|
||||
private parseSimpleCondition(): WhereCondition {
|
||||
// NOT expr
|
||||
if (this.curTokenIs(TokenType.NOT)) {
|
||||
// NOT expr(注意 NOT IN / NOT LIKE 不作为通用 NOT)
|
||||
if (this.curTokenIs(TokenType.NOT) && !this._isNotInOrLike()) {
|
||||
this.nextToken();
|
||||
const inner = this.parseSimpleCondition();
|
||||
return { $not: inner } as unknown as WhereCondition;
|
||||
@@ -483,6 +483,36 @@ export class Parser {
|
||||
return result;
|
||||
}
|
||||
|
||||
// NOT LIKE / NOT IN(NOT 后紧跟 LIKE 或 IN)
|
||||
if (this.curTokenIs(TokenType.NOT)) {
|
||||
if (this.peekTokenIs(TokenType.IN)) {
|
||||
// NOT IN
|
||||
this.nextToken(); // skip NOT
|
||||
this.nextToken(); // skip IN
|
||||
this.expect(TokenType.LPAREN);
|
||||
if (this.curTokenIs(TokenType.SELECT)) {
|
||||
const subquery = this.parseSelect();
|
||||
this.expect(TokenType.RPAREN);
|
||||
const result: WhereCondition = {};
|
||||
result[column] = { $nin: { $subquery: subquery } };
|
||||
return result;
|
||||
}
|
||||
const values = this.parseValueList();
|
||||
this.expect(TokenType.RPAREN);
|
||||
const result: WhereCondition = {};
|
||||
result[column] = { $nin: values };
|
||||
return result;
|
||||
} else if (this.peekTokenIs(TokenType.LIKE)) {
|
||||
// NOT LIKE
|
||||
this.nextToken(); // skip NOT
|
||||
this.nextToken(); // skip LIKE
|
||||
const pattern = this.parseValue();
|
||||
const result: WhereCondition = {};
|
||||
result[column] = { $not: { $like: pattern } };
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// LIKE
|
||||
if (this.curTokenIs(TokenType.LIKE)) {
|
||||
this.nextToken();
|
||||
@@ -541,6 +571,11 @@ export class Parser {
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 判断当前 NOT 是否为 NOT IN / NOT LIKE 的一部分(不应作为通用 NOT 处理) */
|
||||
private _isNotInOrLike(): boolean {
|
||||
return this.peekTokenIs(TokenType.IN) || this.peekTokenIs(TokenType.LIKE);
|
||||
}
|
||||
|
||||
private peekTokenIs(type: TokenType): boolean {
|
||||
return this.peekToken.type === type;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user