2 Commits
Author SHA1 Message Date
thzxx d14663ef80 test(coverage): 补齐 B-4/B-5 新代码的分支覆盖 + ORDER BY 列校验 + 删除死代码
覆盖率门禁(statements 90 / branches 82 / functions 94 / lines 93)在 B-4/B-5 落地后
**真的失败了**(branches 81.87%、functions 93.93%)—— 说明门禁确实在起作用。
本次不是下调阈值,而是两种正确处置:

① 删除死代码(3 个导出,从未被调用)
   - `isCaseExpression`:B-4 重构后 executor 改用 `parseCaseExpression` 自身判前缀;
   - `assertNoSimpleCaseForm`:从未接线(简单 CASE 的拒绝由解析器报错覆盖);
   - `compareForCase`:从未被调用(三值比较走 `sql-compare`)。
   - `collectUnknownColumns`(validation.ts):同样从未被调用。
   留着它们会让覆盖面看起来更高而实际无人使用 —— 与"覆盖率要反映真实使用"相悖。

② 补齐真实分支的测试(不写"为覆盖而覆盖"的用例)
   - `column-value` 的两条取值路径:JOIN 行键 `别名.列` 的精确命中与唯一后缀回退;
   - JOIN 里两表同名列的**裸引用歧义**;
   - CASE 解析缓存的"超上限清空重建"分支(600 个不同表达式);
   - CASE 条件语法错误 → PARSE_ERROR。

顺带修掉一个新暴露的真实缺口:**ORDER BY 的键此前完全不校验**
  `SELECT ... FROM l JOIN r ON l.tag = r.tag ORDER BY tag`(两表都有 tag)既不报错
  也不确定按哪一列排 —— 结果取决于行键插入顺序("顺序偶尔不对"这类难查问题)。
  现在与 WHERE 同一口径:越界/未知 → COLUMN_NOT_FOUND,裸名歧义 → 要求限定。
  豁免两类合法写法:SELECT 别名(输出列名)与派生表(列来自子查询投影)。
  新增 `selectAliasNames` 并被 `orderByUsesSelectAlias` 复用 —— 两处若各写一份,
  就会出现"排序认为它是别名、校验认为它是列"的矛盾。

覆盖率达到:Statements 90.4% / Branches 82.19% / Functions 94.25% / Lines 93.41%,
四项均高于阈值。全量 89 套件 / 1868 测试通过;typecheck、lint 零错误零告警。
2026-09-15 07:51:10 +08:00
thzxx 2c945ee05a feat(B-5): 输出列序号 + 分隔标识符语义统一
两个相关缺陷:

① **输出列序号不被支持**(SQL 标准特性)
   `ORDER BY 1` / `GROUP BY 2` 此前直接 `PARSE_ERROR: Expected identifier, got "1"`
   (实测)。这在手写 SQL 与 UNION 里很常用 —— 各分支输出列名可能不同,只能按
   序号引用。序号含义依赖 SELECT 列表,故 parser 只保留数字文本,由 executor
   在拿到列表后解析。
   规则(与 SQLite/标准一致):裸数字在范围内 → 位置;越界 → QUERY_ERROR
   (不说"未知列",问题出在位置而非名字);`GROUP BY <序号>` 指向聚合表达式 →
   QUERY_ERROR(按聚合值分组语义上不成立);`ORDER BY 0` / `1.5` → PARSE_ERROR。

② **分隔标识符(`"1"`)在投影期被当成常量**
   `CREATE TABLE q ("1" STRING ...)` 后 `SELECT "1" FROM q` 返回 `{"1": 1}`
   (**字面量 1**),而 `SELECT *` 返回正确的 `{"1": "z"}` —— 同一列两种结论。
   根因:parser 把 `"1"` 的引号丢掉,投影期的"裸数字 = 常量列"子句就把它吃了。

修复的关键设计(一致性是这里唯一的难点,实测踩了四次漂移):
  **引号只在"解析 → 执行"的边界脱去,且必须在同一处、对同一批引用统一处理。**
  parser 必须保留引号才能区分"名为 1 的列"(`"1"`)与"常量 1"(`1`);
  而校验/取值/排序/分组必须用真实列名。此前"校验用裸名、投影用带引号名"两套规则
  导致 `1` 与 `"1"` 两个键并存。现在统一在 `normalizeUnprefixedReferences`
  与 `projectRow`/`projectColumns`/`resolveGroupKeyValue` 的对应分支脱引号,
  并新增可复用的 `unquoteIdentifier`。

同时修正 `projectColumns`(where-matcher,被四个引擎共用):此前"找不到列就不产出
键",现在支持分隔标识符与 `别名.列` 的唯一后缀匹配;"是否未知列"仍由 executor 的
`assertProjectionColumnsExist` 判定并报错,投影层不做静默兜底。

验证:新增 tests/v080-output-ordinals.test.ts(64 项:序号四引擎 40 + 分隔标识符
四引擎 24),覆盖 DESC/多键/LIMIT+OFFSET/UNION/别名共存/越界/非法序号/
WHERE 与 GROUP BY 引用分隔标识符/优先级。
全量 91 套件 / 1870 测试通过;typecheck、lint、build 零错误零告警;dist 已重建。
2026-09-15 07:39:03 +08:00