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 已重建。
This commit is contained in:
Vendored
+14
@@ -856,6 +856,20 @@ declare class QueryExecutor {
|
||||
* 于是 `COUNT (n)`(函数名后有空格)在"是否聚合"判定与"如何求值"两处结论不同。
|
||||
*/
|
||||
private computeSingleAggregate;
|
||||
/**
|
||||
* v0.8.0(B-5):把 ORDER BY / GROUP BY 里的**输出列序号**解析为输出列名。
|
||||
*
|
||||
* SQL 标准允许 `ORDER BY 1` / `GROUP BY 2` 按输出列位置引用(UNION 各分支
|
||||
* 列名可能不同,只能按序号引用)。规则:
|
||||
* 1. 若存在**同名的真实列**(如列名就是 `"1"`,用双引号建表),按列名优先 ——
|
||||
* 显式标识符胜过位置简写;
|
||||
* 2. 纯数字 → 第 N 个输出列的**表达式原文**(`SELECT n AS num ... ORDER BY 1`
|
||||
* 解析为 `num`,因为投影后行里只有 `num`);
|
||||
* 3. 序号越界 → `QUERY_ERROR`(不说"未知列",因为问题出在位置而不是名字);
|
||||
* 4. `GROUP BY <序号>` 指向聚合表达式 → `QUERY_ERROR`
|
||||
* (按聚合值分组语义上不成立,SQL 标准同样禁止)。
|
||||
*/
|
||||
private resolveOutputOrdinals;
|
||||
/**
|
||||
* 归一化"行键不带前缀"的查询中的所有引用 —— 剥离表别名前缀。
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user