Repository navigation
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
关联 Issue
Closes #445
效果预览
https://cythia828.github.io/monaco-sql-languages/
在预览地址选择对应方言后,可直接粘贴文末 测试 SQL 中各场景的用例验证补全效果。
问题背景
WITH cte AS (SELECT ...) SELECT * FROM cte这类 CTE 语句,解析器把 CTE 名当成物理表收集:TableDeclareType.LITERAL,与真实表无法区分,下游无法判断该表是虚拟表。relatedEntities为空,补全FROM cte后拿不到cte的列。WITH a AS (SELECT * FROM a)会自引用);WITH Sales_Data AS (...)后FROM sales_data无法识别;SELECT ... FROM未写完(caret 停在 FROM 后)时 CTE 不可见,补全列表为空。JOIN t1 ON、WHERE orders.等位置由于语法无法空匹配,语法建议和表实体可见性都不正确。方案
在
EntityCollector基类引入 「收集 → 延迟绑定」 两阶段机制,各方言只需在对应的 CTE 规则 exit 回调里调用一次collectCte()。1. 收集:
collectCte()+CteDefinition把 CTE 名 push 为
TABLE实体、declareType = EXPRESSION,并记录:endTokenIndex:本 CTE 项的结束 token(不是后续 CTE 的),语义为「该名字在此 token 及之前不可见」;scopeEndTokenIndex:所属查询的结束 token,即该 CTE 的作用域上界;body/columnNameCtxs/columnListCtx:用于推导输出 schema。2. 作用域计算:
cteScopeEndTokenIndex()/extendUnclosedScope()沿父链查找
WithClauseContext/WithStatementContext/CtesContext/WithContext,取其所属查询的stop作为作用域上界;找不到则回退到当前语句范围。针对
SELECT ... FROM(未闭合的 FROM/JOIN 导致解析在 caret 前中断)额外做extendUnclosedScope():从 scopeEnd 向后扫描,跳过 hidden channel 与 EOF,遇到;或「深度为 0 的)」停止,把作用域延伸过去,保证 caret 处 CTE 仍然可见。3. 输出 schema:
finalizeCteDefinitions()优先级:显式列清单 > body 查询的
QUERY_RESULT实体。QUERY_RESULT(wrapOutwardColumns),不与实体集共享对象,避免污染 body 查询自身的列信息;findOutwardQueryResult()在实体集中找被 body token 区间完全覆盖的QUERY_RESULT,同深度取最靠前的那个;adaptOutwardQueryResult()覆盖——Generic 就用selectListText把整条语句文本替换为 select 列表文本;entity.relatedEntities,使 CTE 定义与引用共享同一份 schema。4. 引用绑定:
bindCteReferences()遍历实体集中
declareType === LITERAL的TABLE实体,用resolveCte(name, tokenIndex)找最近的、位于该 token 之后、且作用域覆盖该 token 的同名 CTE,命中则改写为EXPRESSION并关联同一个outward。名字比较经normalizeIdent()处理:去反引号/双引号/方括号,统一小写。5. 可见性:
markCteAccessibility()caret 存在时,用
getPrevNonHiddenTokenIndex()求有效 token 位置,CTE 定义实体仅在resolveCte(自己的名字, caretToken) === 自己时isAccessible = true。由此天然得到正确语义:CTE 在自己 body 内不可见、定义之后可见、后续同名 CTE 覆盖前者。6. 触发时机
exitProgram()中执行一次;语句级 walk(不走 program)则在exitStmt()检测到_stmtStack为空时执行一次,保证两种遍历方式下都完成 finalize + bind。方言接入
exitWithItemwithItemName/columnNameexitCteStatementcolumnNameList().columnName()exitNamedQuerycolumnAliases()exitNamedQueryidentifierList().identifierSeq().errorCapturingIdentifier()exitNamedQuerycolumnAliases().identifier()exitNamedQuerycteColumnList语法 +AliasedRelationContext.name别名exitCommonTableExproptColumnList().columnList().columnName()exitCommonTableExpressions(',' commonTableExpressions)?),ctx.stop是后续 CTE 的结尾,因此取uid()[0]为名字、其余为列名,并单独定位 body 之后的第一个)作为endTokenIndex顺带修复
Generic 收集器
exitColumnDefinition:原先 push 整个columnDefinition,导致列名补全标签变成age int并把类型插入 SQL。改为 pushcolumnRef,再用dataType的源码切片单独填AttrName.colType(含endIndex/endColumn精确位置,支持varchar(50)这类带参数的完整类型)。exitSelectItem:别名直接从ctx._alias取,避免向上搜父节点时串到后一个 select item 的别名、导致所有列被标成同一个名字。index.ts:qualifiedName在expression上下文(且不属于tableName/tableNameCreate)时识别为COLUMN建议,使JOIN t1 ON能给出列建议。语法(caret 空匹配,均受
caretTokenIndex >= 0 && shouldMatchEmpty()语义谓词保护,不影响正常校验)PostgreSqlParser.g4:新增joinCondition规则(joinQual| 空匹配),joinQual的ON增加columnNamePath (EQUAL columnNamePath)?与emptyColumn。SELECT * FROM a JOIN b仍然报错(有测试覆盖)。MySqlParser.g4:joinSpec的ON支持columnNamePath (EQUAL_SYMBOL columnNamePath)?。TrinoSql.g4:primaryExpression增加base '.'的空匹配 dereference,使WHERE orders.时表实体保持可见、能给出列建议。错误上下文过滤
新增
_errorContexts+visitErrorNode:标记所有含解析错误的祖先上下文,pushEntity时跳过。原因是错误恢复会把 rule context 拉长到整个子句,ctxToText会产出JOIN myOwn\nORDER BY 1这类脏文本作为实体名。测试
test/common/cteCases.ts共用用例注册器,6 个用例覆盖:定义/引用收集与 schema、body 内不可见/之后可见、显式列清单作为输出 schema、同名 CTE 绑定最近者、大小写不敏感匹配、未完成 FROM 仍可见。在 flink / generic / hive / impala / mysql / postgresql / spark / trino 八个方言各注册一份cte.test.ts。INSERT(DML 主体)用例、JOIN b无ON/USING校验为非法。WHERE orders.的实体可见性与 suggestion 用例。JOIN t1 ON建议用例与列类型范围用例。其他
package.json版本4.5.1→4.5.2-beta.0。src/lib/**为 ANTLR 生成产物,随.g4改动一并重新生成(占 diff 绝大部分)。影响面与回归建议
EntityCollector基类,影响全部方言;建议重点回归 CTE 补全、表别名补全、CREATE TABLE列补全、JOIN ... ON补全。_errorContexts跳过逻辑会让含解析错误的 SQL 少收集部分实体——但这些实体文本本就不可用,属预期行为。测试 SQL
以下用例均取自本 PR 的单元测试(
test/common/cteCases.ts等),已全部通过。在预览地址选择对应方言后粘贴即可验证,光标位置用|标注(粘贴时请去掉)。1. 基础 CTE 收集与列补全(全部方言)
sales_data、top_products识别为 CTE(declareType = EXPRESSION),orders、products仍为物理表(LITERAL)top_products的输出列取自 body 的 select 列表,SELECT tp.|可补出product_id、total_salesFROM orders的orders上:sales_data、top_products均不可见(CTE 在自身 body 内不可见)FROM sales_data上:sales_data可见,orders不可见2. 显式列清单作为输出 schema
cte的输出列是列清单的pid、sales,而非 body 的product_id、total_sales。3. 同名 CTE 绑定最近的一个
SELECT * FROM cte绑定第二个cte,补出的列是b而不是a。4. 大小写不敏感匹配
FROM sales_data命中上面的 CTE 定义,能补出a。5. 未写完的 FROM 仍能补出 CTE 列(光标放在
FROM之后)sales_data仍然可见,可补出product_id。6.
JOIN ... ON后的列补全(GenericSQL / MySQL / PostgreSQL)ON之后能给出orders、t1两表的列建议。7.
WHERE table.后的列补全(Trino)orders.后能给出orders表的列建议,且orders实体isAccessible为true。8. 缺少
ON/USING的 JOIN 仍报错(PostgreSQL)报语法错误。
校验通过。
9. CTE 用于 DML 主体(PostgreSQL)
cte被识别为 CTE,SELECT * FROM cte处能补出a。