feat(B-4): 统一表达式求值 —— CASE 结构化解析(消除正则切分整类缺陷)
修复前 `parseCaseExpression` 用正则切分 WHEN/THEN/ELSE,不认字符串字面量与嵌套,
实测出三类错误结果:
① **嵌套 CASE 返回字符串残片**
`CASE WHEN n>10 THEN CASE WHEN n>25 THEN 'huge' ELSE 'big' END ELSE 'small' END`
→ 实测返回 `"big' END ELSE 'small"`(正则把嵌套 CASE 的 ELSE 当成自己的分支
边界,残片被原样返回给用户)。修复后正确返回 huge/big/small。
② **条件引用不存在的列时静默错值**
`CASE WHEN nope > 1 THEN 'x' ELSE 'y' END` → 每行都是 'y' 且**无任何报错**
(`cond = null` 表示"解析失败"→ 静默跳过分支),而同一个 nope 写在 WHERE 里
会正常抛 COLUMN_NOT_FOUND。修复后统一报 COLUMN_NOT_FOUND。
③ **`GROUP BY CASE ... END` 完全不可用**
→ `COLUMN_NOT_FOUND Unknown column "CASE WHEN n>10 THEN 'big' ELSE 'small' END"`
(分组把 CASE 原文当成列名)。而"按条件分组"是 SQL 最常见的分析写法之一。
修复后正常输出 `[{band:'big',c:2},{band:'small',c:2}]`。
实现(新增 `src/query/expression.ts`):
- 复用 `sql/lexer` 的 token 流做**递归下降**(天然支持嵌套;字符串里的
WHEN/ELSE/THEN 由词法层天然隔离,不可能被当作切分点);
- 条件按源码切片后交给与 WHERE **完全相同**的 `parseWhereCondition` —— 语义同源;
- 结果表达式显式支持字面量/列引用/嵌套 CASE,无法识别的**抛 NOT_SUPPORTED**
(不再把原文当字符串返回);
- 解析结果记忆化(同一表达式在 N 行上只解析一次);
- 新增 `assertCaseColumnsExist`,在**分组/聚合之前**按 schema 校验 CASE 里引用的列
(时机很关键:分组会把行替换为"分组键+聚合值",此后任何基于行的校验都会
误报未知列 —— 这一点在实现中踩到并已修正)。
顺带修掉的**词法层**缺陷:`Token.position` 语义按 token 类型不一致 ——
`readString` 用 `position + 1`(指向引号**之内**),`readIdentifier`/`readNumber`
用 `position - len`(指向首字符)。任何"按 position 切片"的调用方都会对字符串
切错一个字符(实测 `'big'` 被切成 `"big'"`,导致 CASE 全部报 NOT_SUPPORTED)。
现统一为"token 首字符在源码中的下标",并用"从 position 重新词法化应得到同一
token"作为可判定判据加入回归测试。
验证:新增 tests/v080-case-expression.test.ts(54 项:词法层 2 + 解析层 8 +
求值层 5 + 列校验 2 + 四引擎端到端 37),并做**变异验证**:去掉嵌套 CASE 感知后
7 项立即失败(含四引擎的端到端断言),恢复后全绿。
全量 91 套件 / 1807 测试通过;typecheck、lint、build 零错误零告警;dist 已重建。
This commit is contained in:
Vendored
+8
@@ -776,6 +776,14 @@ declare class QueryExecutor {
|
||||
* 裸列名只要求"某一侧存在"(`ON k = k` 的既有语义是取主表列)。
|
||||
*/
|
||||
private validateJoinOnColumns;
|
||||
/**
|
||||
* v0.8.0(B-4):校验 SELECT / GROUP BY / HAVING / ORDER BY 里 CASE 表达式引用的列存在。
|
||||
*
|
||||
* 为什么单独一个方法:CASE 可以出现在四个子句里,而每个子句的校验时机不同
|
||||
* (WHERE 有 `assertWhereColumnsExist`,投影有 `assertProjectionColumnsExist`)。
|
||||
* 统一在这里按 **schema** 收集可见列,与行形状解耦,避免"分组后校验不到源列"。
|
||||
*/
|
||||
private assertCaseColumnsExist;
|
||||
/**
|
||||
* v0.8.0(A37):校验 WHERE 中出现的列名都存在于行源。
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user