MSSQL Query Span 提取器 Omni 迁移全攻略:Bytebase 从 ANTLR 到自研解析器的渐进式替换实战

MSSQL Query Span 提取器 Omni 迁移全攻略:Bytebase 从 ANTLR 到自研解析器的渐进式替换实战 MSSQL Query Span 提取器 Omni 迁移全攻略Bytebase 从 ANTLR 到自研解析器的渐进式替换实战【免费下载链接】bytebaseDatabase governance built for humans and agents — controlling changes and access across every major database.项目地址: https://gitcode.com/GitHub_Trending/by/bytebase导读本文基于 Bytebase 仓库中的 MSSQL query_span_extractor omni migration 完整方案系统拆解如何将 MSSQL 的 query-span查询跨度敏感字段提取从 ANTLR 语法树逐步迁移到自研 omni AST 之上。读完本文你将掌握query-span 提取的架构与数据流、12 个渐进式迁移阶段的拆解逻辑、表达式解析器对约 30 种 AST 节点的覆盖策略、双路径共存与一次性切换cutover的工程约束以及由 29 个 YAML fixture 与差分测试构成的回归防线。对于关注数据库安全治理、解析器迁移、大规模遗留代码替换的工程师这是一份可复用的实战参考。背景什么是 Query Span为什么它需要 omni在 Bytebase 的数据库治理体系里query span 是一条 SQL 语句在数据平面上的足迹它记录语句访问了哪些表access tables、读写了哪些列source columns、结果集长什么样results、WHERE/HAVING/JOIN ON 中哪些列被用于谓词predicate columns以及语句类型SELECT/INSERT/UPDATE…。这套信息是后续三大消费者的共同地基advisorSQL 审核、masking动态数据脱敏和lineage数据血缘。MSSQL 的 query-span 提取器位于 backend/plugin/parser/tsql/query_span_extractor.go约 3743 行含随后并入的 omni 实现另有独立的 query_span_predicate.go。传统实现建立在 ANTLR 生成的语法树之上先用antlr4-go解析 T-SQL再用一个 visitor/listener 遍历语法树做列级分析。问题的根源在于双解析器并存。Bytebase 自研的 omni 解析器github.com/bytebase/omni/mssql已逐步覆盖 T-SQL 的解析能力其 AST 节点类型更干净、语义信息更完整例如TableRef直接带 server/database/schema/table 字段无需再自行拆分标识符FullTextPredicate是独立的谓词节点。若 query-span 提取继续停留在 ANTLR 上意味着同一份 SQL 要维护两套解析与两套提取逻辑且 ANTLR 生成代码体积大、迭代慢。本次迁移的目标非常明确最终状态MSSQL query-span 提取的每一条代码路径都运行在 omni AST 上query_span_extractor.go与query_span_predicate.go中的 ANTLR 代码被删除tsql/omni.go的 ANTLR 回退被移除TestGetQuerySpanYAML 语料与下游消费者advisor、masking、lineage零回归。从当前仓库代码看这个终态已经实现现在 backend/plugin/parser/tsql/query_span.go 中GetQuerySpan的入口直接调用newOmniQuerySpanExtractor(...)与getOmniQuerySpan(...)注释明确写着 Runs on the omni AST extractor。也就是说本文讲述的方案已经落地仓库现状就是文档终态的最好印证。范围与非目标迁移范围严格限定在query-span 提取相关的文件与逻辑。文档明确列出以下 T-SQL 能力不在本次迁移范围由独立任务跟踪resource_change资源变更backup / restore备份恢复completion补全diagnose诊断get_database_metadata数据库元数据这种范围切割保证了迁移可以被完整评审、独立合并也避免一次改动波及整个 tsql 解析插件。目标架构单一提取器无双解析迁移后的架构是单一提取器single extractor不搞双解析、不做运行时派发no dual-parse。omni 负责产出 AST提取器只负责消费它。GetQuerySpan → ParseTSQL(stmt) → []omnimssql.Statement → pre-pass: collect DECLARE t TABLE into gCtx.TempTables → omniQuerySpanExtractor.run(stmts) → classifyQueryType(stmts[0].AST) (already omni) → if not SELECT: return span with only Type access tables → collectAccessTables(stmts[0].AST) → isMixedQuery check → extractFromSelectStmt(sel) → PseudoTable → predicate-columns collection (uses existing omni helper subquery-output merge) → return QuerySpan对照仓库中的 getOmniQuerySpan 实现这条调用链与现实完全吻合ParseTSQL(statement)得到 omni 的Statement列表omni.go 的 ParseTSQL空语句、GO批次分隔符被特判为返回空 Select span对齐 ANTLR 旧行为DECLARE t TABLE(...)/CREATE TABLE #temp(...)会先把临时表定义灌进gCtx.TempTablespopulateOmniTempTables/populateOmniCreateTempTable供同批次后续语句解析——这对应文档 Phase 8 的多语句预扫描collectOmniAccessTables收集访问表isMixedQuery检测系统表与用户表混用混用直接返回base.MixUserSystemTablesErrorclassifyQueryType判定语句类型非 SELECT 直接返回仅含 Type 与 access tables 的 span真正的 SELECT 进入extractFromSelectStmt产出结果列PseudoTable与谓词列。提取器的内部状态结构体布局与旧 ANTLR 提取器保持一致文档中的设计决策仓库 querySpanExtractor struct 可证ctxcontextdefaultDatabase/defaultSchema默认库与 schemadefaultSchema为空时回退dboignoreCaseSensitive用户可控的标识符大小写敏感开关所有 omni 提取中的标识符比较都必须遵守它见文档 Open Question 5gCtx base.GetQuerySpanContext元数据访问上下文ListDatabaseNamesFunc、GetDatabaseMetadataFuncctes当前作用域可见的 CTE 列表进入 WITH 子句时压栈、退出时截断outerTableSources相关子查询用于解析外层 FROM 列的来源tableSourcesFrom本作用域的 FROM 来源predicateColumns谓词列集合viewResolutionStack视图递归解析防环迁移期间新增的omniQuerySpanExtractor内嵌embed了*querySpanExtractor从而直接复用所有纯字符串/元数据辅助函数而不用维护并行副本同时新增source string字段保存原始 SQL 文本配合 omni 节点自带的Loc从原文切片表达式名对应 sliceName / omniNodeLoc 实现。在 Phase 11 切换完成后wrapper 被删除内嵌结构体改名为唯一的querySpanExtractor。共享辅助函数零并行拷贝文档强调一个工程原则——凡是吃字符串的辅助函数直接复用不创建平行副本。迁移前就已抽取完成Phase 0 ✅辅助函数作用tsqlFindTableSchemaByParts(linkedServer, db, schema, table)将四元组解析为 TableSourcelinked server 非空直接报not supportedtsqlIsFieldSensitive(db, schema, table, column)纯字符串判断字段是否敏感掩码tsqlGetAllFieldsOfTableInFromOrOuterCTE(db, schema, table)从 FROM 或外层 CTE 展开某表的全部字段isIdentifierEqual(a, b)纯函数标识符比较unionTableSources(...TableSource)表来源合并isMixedQuery/isSystemResource系统资源判定getColumnsFromCreateView(definition, db, schema)递归到 query-span 解析视图体切流后自举self-hosting迁移过程中新增的三个 omni 侧函数resolveOmniExpression(ast.ExprNode) (base.QuerySpanResult, error)—— ANTLRgetQuerySpanResultFromExpr的 omni 等价物Phase 1 的主体约 2525 行旧代码的替代extractOmniFromSubquery(sel *ast.SelectStmt) (*base.PseudoTable, error)—— 克隆作用域的降级clone-scope descentcollectOmniAccessTables(ast.Node) base.SourceColumnSet—— 镜像 ANTLR 的 accessTableListener。覆盖地图每一个 ANTLR 入口都有归宿文档用一张覆盖地图确保无死角每个 ANTLR 入口函数都对应明确的 omni 目的地与迁移阶段。这是大型解析器迁移最关键的工程实践——先穷举入口再谈实现。核心映射如下节选ANTLR 函数约 LOCOmni 替换阶段getQuerySpan~85getOmniQuerySpan11切换extractTSqlSensitiveFieldsFromSelectStatementStandalone~110并入extractFromSelectStmtWith set ops1/5/6extractTSqlSensitiveFieldsFromQuerySpecification~75extractFromQuerySpec1/2extractTSqlSensitiveFieldsFromTableSourceItem~75extractTableSourceItem2/3/7getQuerySpanResultFromExpr~2525resolveOmniExpression1最大getAccessTables listener~80collectOmniAccessTables9splitTableNameIntoNormalizedParts等 4 个仅 ANTLR 使用的工具—omni 不需要TableRef自带字段、自动去引号11 丢弃对照仓库现状collectOmniAccessTables、extractFromSelectStmt、extractFromSetOp、processWithClause、resolveExpression等 omni 版函数都已实装见 query_span_extractor.go 函数清单 中的方法定义与文档规划的落点一一对应。十二个迁移阶段详解Phase 0 — 地基已完成✅ 抽出tsqlFindTableSchemaByParts✅collectOmniPredicateColumnRefs 测试✅omniQuerySpanExtractor脚手架支持单表 SELECT、WHERE 谓词、目标列表中裸列/限定列/*/t.*/AS alias五种形态✅ 25 个测试分布在TestOmniQuerySpan_SupportedShapes/_NotFound/_UnsupportedShapes这一阶段的意义是先立骨架再填肉提取器能在真实 YAML 语料之外独立跑通最基础形态为后续每个阶段提供可运行的测试基座。Phase 1 — 表达式解析器resolveOmniExpression最大的一块这是整个迁移中唯一一块大头需要镜像 ANTLRgetQuerySpanResultFromExpr2525 行对每个 omni 表达式节点类型的处理。文档给出了一张完整的节点行为表Omni 节点源列行为*ColumnReftsqlIsFieldSensitive查敏感IsPlainFieldtrue*StarExpr经tsqlGetAllFieldsOfTableInFromOrOuterCTE展开调用方视为多个列*Literal/*VariableRef/ 无限定符标量*StarExpr空源列*BinaryExpr/*UnaryExpr/*BetweenExpr/*LikeExpr/*IsExpr/*InExpr合并子表达式源列*CaseExpr/*IifExpr/*CoalesceExpr/*NullifExpr合并全部分支*CastExpr/*ConvertExpr/*TryCastExpr/*TryConvertExpr解包unwrap*ParenExpr/*CollateExpr/*AtTimeZoneExpr解包*FuncCallExpr合并所有参数递归进 OverClausepartition/order 表达式也贡献源列*MethodCallExpr合并参数*SubqueryExpr标量子查询克隆提取器并携带 outerTableSources取第一个结果列的源列同时把子查询的谓词/源列并入外层 predicateColumns*SubqueryComparisonExprLeft 源列 子查询输出源列*ExistsExpr仅子查询谓词列无值列*FullTextPredicateColumns Value 源列*ResTarget/*SelectAssign解包 ValName 成为结果名*GroupingSetsExpr/*RollupExpr/*CubeExpr合并参数两个值得特别注意的语义点IsPlainField标志语义仅当表达式恰好是*ColumnRef或被*ParenExpr包裹的 ColumnRef时为 truecol0这类算术包装必须为 false。这是下游脱敏/血缘判断该结果列是否直接来自某物理列的关键文档风险登记 R3 也点名此处易分叉要求逐字节镜像 ANTLR 逻辑。窗口函数OVERFuncCallExpr的参数与 OverClause 的 partition/order 表达式都要贡献源列——仓库测试 TestOmniQuerySpan_WindowOrderBySources 与TestOmniQuerySpan_WithinGroupOrderBySources正是这条规则的直接验证。测试要求每种节点类型一个聚焦子测试约 30 个覆盖算术、嵌套 CASE、CAST 链、列的 COALESCE、列参函数、带 OVER 的聚合partition 列与 select 列不同、标量子查询、相关标量子查询。Phase 2 — 多表 FROM JOIN ON处理函数extractFromClause(*ast.List) ([]TableSource, error)遍历 FROM 项JoinClause、TableRef、AliasedTableRef 等结果拼入tableSourcesFromextractTableSourceWithJoins(node ast.Node)*ast.JoinClause递归左右两侧后拼接ON 条件单独预遍历抽取谓词列谓词辅助函数collectOmniSelectPredicateColumnRefs已覆盖此场景其他节点 →extractTableSourceItem关键语义JOIN 的 Left/Right 处于同一作用域FROM 中逗号连接的多个项也同在同一作用域。测试覆盖 INNER/LEFT/RIGHT/FULL/CROSS JOIN、三表链式a JOIN b ON ... JOIN c ON ...、逗号连接与 JOIN 混用、带派生表的 JOIN、USING 子句omni 的JoinClause.Using是*List。Phase 3 — 派生表与列别名列表*AliasedTableRef包裹*SubqueryExpr→ 派生表用克隆提取器继承外层tableSourcesFrom作为 outerTableSources递归别名成为 PseudoTable.Name列别名列表按位置重命名*AliasedTableRef包裹*ValuesClause→ VALUES 派生表每行是一个表达式元组列数以首行为准别名列表重命名FROM 中裸*ValuesClause→ 同上但无别名*AliasedTableRef{Table: *TableRef}上的列别名列表 → 重命名物理列TVF 场景罕见但合法长度不匹配时报错需镜像 ANTLR 的number of column alias %d does not match the number of columns %d。测试用例(SELECT a,b FROM t) AS x、(SELECT a,b FROM t) AS x(c1,c2)、(VALUES (1,2),(3,4)) AS v(a,b)、嵌套派生表、派生表内 ORDER BY/GROUP BY。Phase 4 — 表达式中的子查询与作用域管理这是相关子查询正确性的核心阶段标量子查询resolveOmniExpression调用extractFromSubquery用克隆提取器outerTableSources 包含外层作用域的 tableSourcesFrom外层归属用第一个结果列的 SourceColumns克隆体的 predicateColumns 合并回外层*ExistsExpr同样克隆作用域只贡献谓词列无结果值*InExpr.Subquery/*SubqueryComparisonExpr.Subquery克隆作用域结果 SourceColumns 合并进外层 predicateColumns文档特别强调这正是当前谓词辅助函数刻意跳过的子查询输出作为外层谓词场景query_span_predicate.go 的注释明确列出该局限在 Phase 4 由作用域克隆补齐。Phase 4 完成后谓词辅助函数的局限自然消失。测试内部引用外层别名的相关子查询、2/3 层嵌套相关、内外同名列的 IN 子查询、EXISTS 内层谓词 外层相关。Phase 5 — 集合运算*ast.SelectStmt.Op∈ {Union, Intersect, Except} 且带 Larg/Rarg 时递归 Larg → 锚定结果anchor results递归 Rarg → 新结果列数必须匹配每列合并 SourceColumns锚定侧的名字胜出All标志只影响运行时语义不影响列集合注意链式A INTERSECT B EXCEPT C在 omni 中表现为嵌套的 SelectStmt 树。仓库的 extractFromSetOp 已实现该逻辑并且有专门的测试TestOmniQuerySpan_CTEVisibleInSetOpArms验证 WITH 子句在两个集合运算臂中都可见对应extractFromSelectStmt中先处理 WithClause 再派发集合运算的顺序。测试用例A UNION B、A UNION ALL B、链式A INTERSECT B EXCEPT C、派生表内的集合运算。Phase 6 — CTEWITH 子句在处理语句体之前预扫描 WithClause每个*ast.CommonTableExpr用空 tableSourcesFrom 克隆提取器CTE 能通过 q.ctes 看到前面的CTE但看不到外层 FROM提取 CTE 体递归CTE 体是*ast.SelectStmt若.Columns非空则按位置重命名构造 PseudoTableCTE 名 结果列压入q.ctes顺序敏感后面的 CTE 才能看到前面的递归 CTE 在 ANTLR 旧代码中也未显式处理omni 保持一致行为。仓库 processWithClause 与extractCTEBody即为该阶段产物。测试简单 CTE、显式列清单 CTE、CTE2 引用 CTE1 的链条、CTE 遮蔽物理表名、深层嵌套 CTE 引用。Phase 7 — 特殊表来源特殊来源omni 处理PIVOT语义上结果列 源表非 pivot 列减去 FOR 列 每个 IN 值一列pivot 聚合。但ANTLR 旧代码返回 not supported yetomni 用类型化 unsupported 错误保持行为一致真正的 PIVOT 支持是独立后续项目UNPIVOT同上保持 unsupportedCROSS APPLY / OUTER APPLY*JoinClause{Type: CrossApply|OuterApply}Right 是表表达式常为 TVF 或子查询可引用 Left 列相关视为 Right 在包含 Left 的作用域中求值的 JOIN两侧都贡献 tableSourcesFrom表值函数TVFFROM 位置的*FuncCallExpr用户 TVF 需 schema 元数据查返回签名复杂omni 硬编码常见系统 TVFOPENJSON、STRING_SPLIT未知 TVF 返回类型化 unsupported 错误临时表#t→ 从 gCtx.TempTables 取 PseudoTabletTableVarRef同理TableVarMethodCallRefXML.nodes()产出 XML 片段表列以方法别名列命名无物理来源CHANGETABLE返回 PseudoTable仅统计无敏感数据流对齐 ANTLR文档在此明确一个原则新语义工作不要混进迁移。PIVOT 的真实推断、TVF 元数据查询都被推迟为独立 ticket迁移只保证行为等价等价于 unsupported。仓库中 resolveTableValuedFunction 与测试TestOmniQuerySpan_SystemTableValuedFunctions、TestOmniQuerySpan_ApplyRightSideCanReferenceLeft印证了 CROSS/OUTER APPLY 的相关性与系统 TVF 硬编码策略。测试声明并使用临时表、t表变量、XML nodes、CROSS/OUTER APPLY、OPENJSON(...)、CHANGETABLE。Phase 8 — 多语句预扫描临时表ANTLR 旧代码通过 listener 跑完GetQuerySpan的全部语句在处理最终 SELECT 前先吸收前面语句的DECLARE t TABLE。omni 需要同样能力preCollectTempTables(stmts []omnimssql.Statement)遍历每条语句每个VariableDecl.IsTable true的*ast.DeclareStmt从VariableDecl.TableDef抽取列、构造 PseudoTable、放入gCtx.TempTables[name]另处理CREATE TABLE #temp (...)名字以#开头的*CreateTableStmt同样预收集该阶段在仓库中的实现是populateOmniTempTables/populateOmniCreateTempTable由getOmniQuerySpan在入口处调用并有完整测试覆盖TestOmniQuerySpan_DeclareTableVariableRoundTrip、TestOmniQuerySpan_TempTableDefinitions、大小写不敏感版TestOmniQuerySpan_TempTableDefinitionsCaseInsensitive、SELECT INTO注册临时表列的TestOmniQuerySpan_SelectIntoTempTableRegistersColumns。测试用例DECLARE t TABLE(a INT, b INT); SELECT a FROM t、CREATE TABLE #tmp (id INT); SELECT id FROM #tmp。Phase 9 — Access Tables 收集collectOmniAccessTables(root ast.Node) base.SourceColumnSet需要遍历整个 AST用ast.Inspect走全树收集出现在表来源位置FromClause、JoinClause、PivotExpr.Source、派生表、CTE 体 FROM、子查询 FROM的*ast.TableRef排除FuncCallExpr.Name——限定函数名恰好也是*TableRef类型但那是函数不是表文档承认区分两者很棘手给出的方案是不做 inspect-everywhere而是显式只沿携带表来源的字段行走从 SelectStmt 出发下探 FromClause 子查询 CTE跳过 TargetList、WhereClause 表达式除非通过 SubqueryExpr 进入子查询的 FromClause。对等校验parity check对每个 fixture 同时运行 ANTLR 的getAccessTables与 omni 的collectOmniAccessTables断言集合相等。仓库中TestOmniQuerySpan_InListAccessTables就是该方向的具体测试。Phase 10 — INTO、FOR XML/JSON、OPTION、hintsSELECT INTO target_table目标成为写资源不参与敏感字段提取但出现在 access tables 中作为目标ANTLR 旧代码也没有特殊标记omni 对齐FOR XML/FOR JSON结果变成单列文本 blob旧提取器不做特殊处理正常提取omni 对齐。文档备注后续可能做单列折叠single-column collapseOPTION (...)/ 表提示忽略不影响列流TABLESAMPLE忽略仅行级测试SELECT INTO #tmp FROM t临时表目标、FOR XML AUTO保证不崩溃、输出与现行为一致。Phase 11 — 切换Cutover这是决定性的合并闸门GetQuerySpan停止使用 ANTLR入口切换为getOmniQuerySpan删除query_span_predicate.go删除querySpanExtractor中所有吃 ANTLR 上下文的方法已被 omni 版本取代omniQuerySpanExtractor改名querySpanExtractor结构体合并删除仅 ANTLR 使用的辅助函数normalizeFullTableNameFallback、splitTableNameIntoNormalizedParts、unquote、getSelectBodyFromCreateView、NormalizeTSQLIdentifier需 grep 确认无其他使用方omni.go的AsANTLRAST()惰性 ANTLR 回退检查是否还有其他 tsql 文件使用无则删除切换前提全部 YAML fixture 通过、解析器不变量测试TestOmniQuerySpanParserInvariants通过、新提取器测试全绿、lint 干净、构建通过。仓库现状印证切换已完成GetQuerySpan已是 omni 入口且backup_test.go/completion_test.go中已有源文件不得包含github.com/antlr4-go/antlr/v4的断言说明 ANTLR 依赖正被逐模块清除。Phase 12 — 收尾删除已完全替换的query_span_extractor.go中 ANTLR 代码保留解析器 AST 形状覆盖为query_span_parser_invariant_test.go仓库已存在该文件更新AGENTS.md/ skill 文档中旧文件布局的引用为推迟项开 follow-up ticketPIVOT/UNPIVOT 结果列推断、TVF 元数据、SELECT INTO 目标标记、FOR XML 单列折叠测试策略三层次防线1. 现有语料对等仅在切流时把关TestGetQuerySpan的 YAML 语料29 个用例分布在 6 个文件位于 backend/plugin/parser/tsql/test-data/query-span/case-sensitivity.yaml、join.yaml、predicate.yaml、query_type.yaml、regression.yaml、standard.yaml在 Phase 11 切流之后必须原样通过。切流之前omni 提取器是独立代码路径只跑自己的单测ANTLR 仍是主路径——阶段之间不需要让语料经 omni 路径保持绿色一次性在切流时验收。2. 每阶段单测每个阶段在query_span_extractor_omni_test.go增加聚焦测试块每阶段目标 15–30 例按阶段命名Phase 1:TestOmniQuerySpan_ExpressionsPhase 2:TestOmniQuerySpan_JoinsPhase 3:TestOmniQuerySpan_DerivedTablesPhase 4:TestOmniQuerySpan_SubqueriesPhase 5:TestOmniQuerySpan_SetOpsPhase 6:TestOmniQuerySpan_CTEPhase 7:TestOmniQuerySpan_SpecialTableSourcesPhase 8:TestOmniQuerySpan_TempTablesPhase 9:TestOmniQuerySpan_AccessTablesPhase 10:TestOmniQuerySpan_IntoForClauses仓库现状中TestOmniQuerySpan_*系列测试全部实装于 query_span_extractor_test.go涵盖派生表、VALUES、子查询错误传播、空语句与 GO 等验证了方案中每阶段增量测试的执行。3. 差分测试开发辅助非 CI 门禁新增仅测试可用的diffQuerySpan(ctx, stmt, ...)同一输入同时跑 ANTLR 与 omni 两条路径报告结构差异结果数量、名称、源列集合、谓词列、access tables、NotFoundError 对等性。在完整 YAML 语料上本地运行作为进度温度计TestQuerySpan_AntlrOmniParity预期在 Phase 10 完成前一直有 diffPhase 11 时必须为 0 diff这就是切流闸门。该测试随 Phase 12 一并删除。4. Oracle 测试可选切流后用 testcontainer 起真实 MSSQL对每个 YAML fixture 执行 SQL用sys.dm_exec_describe_first_result_set等捕获服务器视角的结果列校验我们提取的列名与服务器一致。不阻塞切流但作为后续很有价值的验证。双路径共存无派发无开关开发期间两条路径在分支内共存但没有运行时派发无 flag、无环境变量。omni 提取器作为包内平行实现活在query_span_extractor_omni.goGetQuerySpan保持现状继续调用 ANTLR 路径。验证 omni 进度靠两件事单元测试TestOmniQuerySpan_*直接调用newOmniQuerySpanExtractor(...).getOmniQuerySpan()绕过GetQuerySpan——每个阶段的覆盖面在此证明对等 harnessTestQuerySpan_AntlrOmniParity仅开发期表驱动跑 YAML 语料同时调 ANTLR经GetQuerySpan与 omni直接报告差异。不支持的 omni 构造在生产路径返回类型化 unsupported 错误。Phase 11 时GetQuerySpan重写为直接调用 omni 提取器无回退开发期的 unsupported 哨兵随 ANTLR 提取代码一并删除。合并策略是硬约束所有阶段在同一分支上隔离进行只有完整迁移完成、ANTLR 路径彻底消失后才合并分支——中间不发布、主分支无 feature flag、生产环境无双路径。这避免了双解析器并存长期赖在主线上的技术债。风险登记十个已知风险与对策#风险概率影响缓解R1omni 解析器卡在异常 fixture 形态低探测 29/29高探测测试已就位每阶段前扩展R2子查询作用域 bug外层引用解析到错误别名中高逐字节镜像 ANTLR 的克隆提取器模式显式测试三层相关子查询 别名遮蔽R3IsPlainField标志分叉ANTLR 把(col)视为 plaincol0不是中中resolveOmniExpression 中镜像精确逻辑diff 测试兜底R4CTE 作用域/顺序差异omni 可能重排 CTE低中按 omni 顺序使用 CTE 项测 3 个 CTE 的链条R5临时表预扫描漏形态带约束的CREATE TABLE #t、SELECT INTO #t中中同时处理 DeclareStmt 与 CreateTableStmt两者都测R6Access tables 集合与 ANTLR 的 accessTableListener 不一致中中每个 fixture 额外语料上跑 diff 测试R7视图体解析递归——畸形视图定义导致 omni 内 omni 死循环低低沿用现有newQ.getQuerySpan(q.ctx, selectBody)模式递归上限由解析器保证R8PIVOT/UNPIVOT 回归ANTLR 对 pivot 返回空omni 可能产出不同结果低低Phase 7 模拟 ANTLR 现状unsupportedPIVOT 支持另立项目R9链接服务器srv.db.schema.obj当前报错omni 保留 Server 字段可能误开始解析它低中保留tsqlFindTableSchemaByParts中的if server ! 提前返回R10CONTAINS/FREETEXT 成为专用*FullTextPredicate节点569d6f7 新增确保 resolveOmniExpression/谓词辅助处理它低已测中谓词辅助已处理resolveOmniExpression 必须包含该 case这些风险的共性缓解手段是差分测试只要 ANTLR 与 omni 双跑任何语义分叉都会被进度温度计暴露。仓库 query_span_predicate.go 中collectOmniPredicateColumnRefs对*ast.FullTextPredicate的显式处理遍历 Columns、Value、LanguageTerm正是 R10 落地的代码证据。工期估算聚焦、可中断的工程师天数阶段估时备注Phase 1表达式2d大头约 30 种节点类型 IsPlainField 边界Phase 2JOIN0.5dPhase 3派生表 列别名0.5dPhase 4子查询 作用域1d作用域克隆最棘手Phase 5集合运算0.5dPhase 6CTE0.5dPhase 7特殊表来源1dCROSS APPLY 相关 TVF 需谨慎Phase 8多语句预扫描0.5dPhase 9access tables 对等0.5dPhase 10INTO/FOR/hints0.25dPhase 11切流 清理0.75dPhase 12收尾0.25d合计约 8 工程日每阶段后修好对等测试保持 CI 绿未决问题与决策记录文档在结尾记录了五个开放问题其中三个已有明确结论解析器 AST 形状覆盖是否保留已解决有用的覆盖保留为query_span_parser_invariant_test.go仓库已存在见 query_span_parser_invariant_test.go。PIVOT 完整支持放本次还是以后当前 ANTLR 是 no-opnot supported。建议 Phase 7 保持 no-op 对等真正的 PIVOT 支持 新项目。TVF 返回形状元数据是否像ListDatabaseNamesFunc一样加 gCtx 函数建议是但推迟到独立 ticket迁移期用空回退。tableSourcesFrom在逗号连接 vs JOIN 间的顺序ANTLR 从左到右appendJOIN 从内向外展开。必须精确一致。大小写敏感开关的贯穿GetQuerySpan的ignoreCaseSensitive由用户控制omni 提取中的所有标识符比较必须遵守——已通过内嵌*querySpanExtractor继承仅需提醒。完成定义合并闸门 Checklist作为迁移类工程的验收范本文档给出了可勾选的完成定义Phase 1–10 各自代码 每阶段单测 lint 干净 go build通过分支内增量进行对等 harnessTestQuerySpan_AntlrOmniParity在完整 YAML 语料上0 diffPhase 11 切流GetQuerySpan直接调用 omni 提取器无回退、无环境变量query_span_predicate.go删除吃 ANTLR 上下文的方法移除TestGetQuerySpan29 个 fixture原样通过Phase 12query_span*.go中不再出现antlr4-go引用omni.go的AsANTLRAST回退经评审后删除若无其他 tsql 文件使用对等 harness 删除go build ./backend/bin/server/main.go通过golangci-lint run ./backend/plugin/parser/tsql/...干净只有以上全部满足才合并分支从方案到现实仓库当前状态对照阅读本文时对照仓库现状你会发现方案已完整落地GetQuerySpan入口已是纯 omni 实现query_span.goomniQuerySpanExtractor覆盖了文档规划的全部阶段产物表达式、JOIN、派生表、集合运算、CTE、特殊表来源、临时表、access tables、SELECT INTOTestOmniQuerySpan_*测试体系、解析器不变量测试、伪列测试query_span_pseudo_column_test.go均已在位。如果你正在做类似的解析器替换这份文档代码的组合本身就是一套完整的迁移方法论模板穷举入口映射、阶段化增量、差分测试温度计、一次性切流、严格合并闸门。【免费下载链接】bytebaseDatabase governance built for humans and agents — controlling changes and access across every major database.项目地址: https://gitcode.com/GitHub_Trending/by/bytebase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考