修复:MySQL 等方言下 WHERE/FROM 补全被字符串、注释或特殊字符标识符误判 - #6774
Conversation
t8y2
left a comment
There was a problem hiding this comment.
当前 tokenizer 对 MySQL 双引号字符串的处理仍有 correctness blocker:只有位于比较运算符后的双引号内容会被当作字符串屏蔽;函数参数或 CASE 返回值中的双引号字符串仍会被识别成 quoted identifier。
例如下面两个场景都会错误产生 ghost_table / ghost_case 表引用:
SELECT CONCAT("from ghost_table", name) FROM real_table WHERE
SELECT CASE WHEN enabled THEN "from ghost_case" ELSE "ok" END FROM real_table WHEREPR 现有 111 个 completion 测试通过,但新增的这两个定向探针均失败。请根据当前 MySQL SQL mode 明确区分双引号字符串与标识符,不要依赖前一个 token 是否为比较运算符,并为函数参数、CASE、嵌套表达式及 ANSI_QUOTES 模式增加回归测试。
…not just after operators
Problem: maskSqlLiteralsAndComments only masked a double-quoted "..." span
as a value when it immediately followed a comparison operator (col = "x"),
leaving it readable as a quoted identifier everywhere else. Under MySQL's
actual default sql_mode (ANSI_QUOTES disabled), "..." is a plain string
literal everywhere, not just after "=" -- so a double-quoted string inside
a function argument (CONCAT("from ghost_table", name)) or a CASE branch
(THEN "from ghost_case") still misread its text as a real table reference.
Solution: tokenizeSqlSemantic gains an opt-in mysqlDoubleQuoteIsString
option that tokenizes "..." as a string (same escaping rules as '...')
instead of a quoted identifier. maskSqlLiteralsAndComments now enables it
for MySQL-family dialects (mysql, doris, starrocks -- the same scope
already used for MySQL's other sql_mode-governed lexical quirks), so
double-quoted strings are masked unconditionally rather than only in the
narrow after-operator position. Non-MySQL dialects (Postgres, SQL Server,
generic), where "..." unconditionally means identifier quoting, keep the
prior operator-adjacency masking for the col = "value" shape.
This is a deliberate tradeoff: a MySQL connection actually running with
ANSI_QUOTES enabled would no longer resolve a double-quoted FROM target
(e.g. FROM "orders") as a table, since that mode can't be observed at
completion time. This matches MySQL's actual default and removes a class
of false-positive ghost-table misdetections that affect every default-
sql_mode MySQL user.
Addresses review feedback on t8y2#6774.
|
已修复,思路调整为:不再依赖"前一个 token 是否为比较运算符"这种位置启发式判断,而是直接在 tokenizer 层面为 MySQL 系方言(mysql/doris/starrocks)新增 SELECT CONCAT("from ghost_table", name) FROM real_table WHERE
SELECT CASE WHEN enabled THEN "from ghost_case" ELSE "ok" END FROM real_table WHERE两个探针用例复测均只识别出 对于 Postgres/SQL Server 等 已知取舍:由于完成层无法感知连接实际的运行时 sql_mode,若某个 MySQL 连接确实开启了 ANSI_QUOTES, 新增/调整了 CONCAT、CASE、嵌套表达式及 ANSI_QUOTES 模式相关的回归测试,全部通过。 |
t8y2
left a comment
There was a problem hiding this comment.
原来的默认 MySQL ghost-table 问题已经修复,但当前实现明确引入了一个新的兼容性回归:maskSqlLiteralsAndComments() 对所有 MySQL-family 连接都把 "..." 当字符串,因此启用 ANSI_QUOTES 的连接不再能从 FROM "orders" 中识别 orders。
MySQL 官方说明,启用 ANSI_QUOTES 后双引号是合法的 identifier quote,并且双引号内容不再是字符串:
https://dev.mysql.com/doc/refman/8.4/en/identifiers.html
Exact-head 探针结果:
CONCAT("from ghost_table", ...)和 CASE 中的默认模式字符串:通过,不再产生 ghost table。SELECT * FROM "orders" WHERE(MySQLANSI_QUOTES):失败,referencedTables返回空数组。- PR 原有 tokenizer/completion 测试:122/122 通过;新增兼容探针 1/2 失败。
代码注释中将这个回归描述为“accepted tradeoff”,但 DBX 不能用常见默认模式静默破坏已有的合法 SQL 模式。请让 completion 能根据实际 session sql_mode 选择语义,或者采用同时保留 table-identifier 位置并屏蔽函数参数、CASE/value 位置的解析策略,并补充默认模式与 ANSI_QUOTES 两套回归测试。
…n, not dialect default Addresses review feedback on t8y2#6774: unconditionally masking "..." as a string literal for every MySQL-family connection broke ANSI_QUOTES connections, where "..." is legitimate identifier quoting (FROM "orders" stopped resolving orders as a table). sql_mode isn't observable at parse time, so instead of guessing a dialect-wide default, "..." right after a table-reference introducer (FROM/JOIN/STRAIGHT_JOIN/UPDATE/APPLY, or a dotted qualifier continuation) is now left unmasked as a potential identifier, while every other position (function args, CASE branches, operator-adjacent values) still masks it as a value. This is correct under both sql_modes at once, without needing to know which one is actually in effect. Also decouples how a "..." span is scanned (backslash-escape-aware when mysqlBackslashEscape is set) from what kind it's tagged as, fixing a latent desync for Hive/Impala/Spark identifiers containing an escaped quote. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
已修复该兼容性回归,思路调整为按 token 位置解析,而不是按方言猜一个全局默认:
SELECT * FROM "orders" WHERE -- ANSI_QUOTES: orders 仍被识别为表
SELECT CONCAT("from ghost_table", name) FROM real_table WHERE -- 默认模式:不再产生 ghost_table新增回归测试覆盖: 复测结果: |
…not just after operators
Problem: maskSqlLiteralsAndComments only masked a double-quoted "..." span
as a value when it immediately followed a comparison operator (col = "x"),
leaving it readable as a quoted identifier everywhere else. Under MySQL's
actual default sql_mode (ANSI_QUOTES disabled), "..." is a plain string
literal everywhere, not just after "=" -- so a double-quoted string inside
a function argument (CONCAT("from ghost_table", name)) or a CASE branch
(THEN "from ghost_case") still misread its text as a real table reference.
Solution: tokenizeSqlSemantic gains an opt-in mysqlDoubleQuoteIsString
option that tokenizes "..." as a string (same escaping rules as '...')
instead of a quoted identifier. maskSqlLiteralsAndComments now enables it
for MySQL-family dialects (mysql, doris, starrocks -- the same scope
already used for MySQL's other sql_mode-governed lexical quirks), so
double-quoted strings are masked unconditionally rather than only in the
narrow after-operator position. Non-MySQL dialects (Postgres, SQL Server,
generic), where "..." unconditionally means identifier quoting, keep the
prior operator-adjacency masking for the col = "value" shape.
This is a deliberate tradeoff: a MySQL connection actually running with
ANSI_QUOTES enabled would no longer resolve a double-quoted FROM target
(e.g. FROM "orders") as a table, since that mode can't be observed at
completion time. This matches MySQL's actual default and removes a class
of false-positive ghost-table misdetections that affect every default-
sql_mode MySQL user.
Addresses review feedback on t8y2#6774.
…n, not dialect default Addresses review feedback on t8y2#6774: unconditionally masking "..." as a string literal for every MySQL-family connection broke ANSI_QUOTES connections, where "..." is legitimate identifier quoting (FROM "orders" stopped resolving orders as a table). sql_mode isn't observable at parse time, so instead of guessing a dialect-wide default, "..." right after a table-reference introducer (FROM/JOIN/STRAIGHT_JOIN/UPDATE/APPLY, or a dotted qualifier continuation) is now left unmasked as a potential identifier, while every other position (function args, CASE branches, operator-adjacent values) still masks it as a value. This is correct under both sql_modes at once, without needing to know which one is actually in effect. Also decouples how a "..." span is scanned (backslash-escape-aware when mysqlBackslashEscape is set) from what kind it's tagged as, fixing a latent desync for Hive/Impala/Spark identifiers containing an escaped quote. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4646555 to
c6e88d1
Compare
…not just after operators
Problem: maskSqlLiteralsAndComments only masked a double-quoted "..." span
as a value when it immediately followed a comparison operator (col = "x"),
leaving it readable as a quoted identifier everywhere else. Under MySQL's
actual default sql_mode (ANSI_QUOTES disabled), "..." is a plain string
literal everywhere, not just after "=" -- so a double-quoted string inside
a function argument (CONCAT("from ghost_table", name)) or a CASE branch
(THEN "from ghost_case") still misread its text as a real table reference.
Solution: tokenizeSqlSemantic gains an opt-in mysqlDoubleQuoteIsString
option that tokenizes "..." as a string (same escaping rules as '...')
instead of a quoted identifier. maskSqlLiteralsAndComments now enables it
for MySQL-family dialects (mysql, doris, starrocks -- the same scope
already used for MySQL's other sql_mode-governed lexical quirks), so
double-quoted strings are masked unconditionally rather than only in the
narrow after-operator position. Non-MySQL dialects (Postgres, SQL Server,
generic), where "..." unconditionally means identifier quoting, keep the
prior operator-adjacency masking for the col = "value" shape.
This is a deliberate tradeoff: a MySQL connection actually running with
ANSI_QUOTES enabled would no longer resolve a double-quoted FROM target
(e.g. FROM "orders") as a table, since that mode can't be observed at
completion time. This matches MySQL's actual default and removes a class
of false-positive ghost-table misdetections that affect every default-
sql_mode MySQL user.
Addresses review feedback on t8y2#6774.
…n, not dialect default Addresses review feedback on t8y2#6774: unconditionally masking "..." as a string literal for every MySQL-family connection broke ANSI_QUOTES connections, where "..." is legitimate identifier quoting (FROM "orders" stopped resolving orders as a table). sql_mode isn't observable at parse time, so instead of guessing a dialect-wide default, "..." right after a table-reference introducer (FROM/JOIN/STRAIGHT_JOIN/UPDATE/APPLY, or a dotted qualifier continuation) is now left unmasked as a potential identifier, while every other position (function args, CASE branches, operator-adjacent values) still masks it as a value. This is correct under both sql_modes at once, without needing to know which one is actually in effect. Also decouples how a "..." span is scanned (backslash-escape-aware when mysqlBackslashEscape is set) from what kind it's tagged as, fixing a latent desync for Hive/Impala/Spark identifiers containing an escaped quote. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
c6e88d1 to
d24c035
Compare
t8y2
left a comment
There was a problem hiding this comment.
维护者冲突代解决说明(已推送):
- 旧 head:
c6e88d1a→ 新 head:d24c0350(rebase 到 mainf41b5dbd,备份 ref 已留) - 冲突原因:main 侧 #7329(SQL Server datepart 补全)与本 PR 在
sqlCompletion.ts两处相邻改动:getSqlCompletionContext内 main 插入的sqlServerDatepartCompletionValues调用行,与本 PR 给isCreateTableColumnTypeContext/preferredKeywordsForCompletion加options.databaseType参数的行相邻 —— 解法为逐字并集,双方都保留;- main 在
isCreateTableColumnTypeContext上方新增的sqlServerDatepartCompletionValues函数,与本 PR 修改该函数签名(maskSqlLiteralsAndComments)相邻 —— 解法为保留 main 新函数 + 本 PR 的新签名。
- 语义交互核查:datepart 路径走
findActiveFunctionCall(原始 beforeCursor),sqlserver 不在 MySQL 屏蔽方言列表内,与本 PR 的双引号屏蔽策略无语义交叠。 - 验证:rebase 后
git diff --check干净、无冲突标记、变更仍为原 4 个文件、无依赖/lockfile 变更;range-diff逐提交映射一致;全量 sql 测试目录 47 文件 1144 用例连续两轮全过(含 #7329 的 datepart 用例);tsc --noEmit0 错误。 - 两个此前的 CHANGES_REQUESTED blocker(函数参数/CASE 双引号字符串误判、ANSI_QUOTES 兼容)已在
c6e88d1a验证解决,本次 rebase 未改动相关逻辑。
… table or clause keywords
Problem: extractReferencedTables and several WHERE/FROM/JOIN-keyword
detectors scanned raw SQL text for keywords without masking string
literals, comments, or special-character identifiers first. Text
that merely appeared inside a string ('from 测试表'), a comment
(-- from ghost_table), or a backtick/bracket-quoted identifier could
be misread as a real table reference or clause keyword. Unquoted
non-ASCII (e.g. Chinese) schema/table names also failed to match the
table-reference pattern used by WHERE-clause column completion.
Solution: mask string literals and comments via the existing
dialect-aware tokenizeSqlSemantic lexer before scanning for
keywords/table references, instead of blind regex/character scans.
Double-quoted spans are disambiguated by syntactic position (masked
as a value only right after a comparison operator) rather than a
single global per-dialect flag, since MySQL's own interpretation of
`"..."` depends on the runtime sql_mode this code can't observe.
Extended unquoted-identifier matching to support non-ASCII characters
for dialects that allow them (MySQL, Postgres, SQL Server, ...),
fixing WHERE-clause column completion for Chinese schema/table names.
Also fixes: MySQL backslash-escaped string literals (dialect-gated
to avoid misreading a trailing backslash in dialects that don't use
it, e.g. a Postgres Windows-path literal), MySQL `#`/`--` comment
handling, backtick/bracket-quoted identifiers containing special
characters, and generalizes the same masking to every other
keyword-detection call site in sqlCompletion.ts that had the same
exposure (isColumnCompletionExpressionStart, hasSelectListExpression,
isAfterSelectBodyExpression, isAfterConditionExpression,
isAfterJoinModifierContext, isInTableListContext,
detectOracleTableFunctionContext, isCreateTableColumnTypeContext).
Fixes t8y2#6496
…not just after operators
Problem: maskSqlLiteralsAndComments only masked a double-quoted "..." span
as a value when it immediately followed a comparison operator (col = "x"),
leaving it readable as a quoted identifier everywhere else. Under MySQL's
actual default sql_mode (ANSI_QUOTES disabled), "..." is a plain string
literal everywhere, not just after "=" -- so a double-quoted string inside
a function argument (CONCAT("from ghost_table", name)) or a CASE branch
(THEN "from ghost_case") still misread its text as a real table reference.
Solution: tokenizeSqlSemantic gains an opt-in mysqlDoubleQuoteIsString
option that tokenizes "..." as a string (same escaping rules as '...')
instead of a quoted identifier. maskSqlLiteralsAndComments now enables it
for MySQL-family dialects (mysql, doris, starrocks -- the same scope
already used for MySQL's other sql_mode-governed lexical quirks), so
double-quoted strings are masked unconditionally rather than only in the
narrow after-operator position. Non-MySQL dialects (Postgres, SQL Server,
generic), where "..." unconditionally means identifier quoting, keep the
prior operator-adjacency masking for the col = "value" shape.
This is a deliberate tradeoff: a MySQL connection actually running with
ANSI_QUOTES enabled would no longer resolve a double-quoted FROM target
(e.g. FROM "orders") as a table, since that mode can't be observed at
completion time. This matches MySQL's actual default and removes a class
of false-positive ghost-table misdetections that affect every default-
sql_mode MySQL user.
Addresses review feedback on t8y2#6774.
…n, not dialect default Addresses review feedback on t8y2#6774: unconditionally masking "..." as a string literal for every MySQL-family connection broke ANSI_QUOTES connections, where "..." is legitimate identifier quoting (FROM "orders" stopped resolving orders as a table). sql_mode isn't observable at parse time, so instead of guessing a dialect-wide default, "..." right after a table-reference introducer (FROM/JOIN/STRAIGHT_JOIN/UPDATE/APPLY, or a dotted qualifier continuation) is now left unmasked as a potential identifier, while every other position (function args, CASE branches, operator-adjacent values) still masks it as a value. This is correct under both sql_modes at once, without needing to know which one is actually in effect. Also decouples how a "..." span is scanned (backslash-escape-aware when mysqlBackslashEscape is set) from what kind it's tagged as, fixing a latent desync for Hive/Impala/Spark identifiers containing an escaped quote. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
d24c035 to
55a237a
Compare
t8y2
left a comment
There was a problem hiding this comment.
两轮 CHANGES_REQUESTED 的 blocker 均已在 c6e88d1a 验证解决,予以通过:
- 函数参数/CASE/嵌套表达式中的双引号字符串不再被解析为表引用(CONCAT/CASE 探针有逐字回归测试);
FROM "orders"在 ANSI_QUOTES 语义下仍识别为表引用(含注释穿插、限定名、JOIN/UPDATE 场景),默认模式与 ANSI_QUOTES 两套回归测试齐备。
维护者已代解与 #7329 的 sqlCompletion.ts 冲突并 rebase 到最新 main(详见此前评论);期间发现并修复了 main 上 #7326 引入的 Keyboard 图标 mock 缺失(caaa893)。当前 head 55a237aa3 本地 sql 全目录 + 相关组件测试 1153/1153 通过、tsc 零错误,CI frontend/changes/label 全绿。
|
Thanks for the contribution! Merged in 4a3d63b, will be released in the next version. |
问题
SQL 补全在提取"当前语句引用了哪些表"、以及判断"光标是否在 WHERE/FROM/JOIN 等关键字之后"时,是靠正则在原始文本上直接扫描
from/join/where等关键字,会被字符串字面量、注释、带特殊字符的引号标识符里恰好出现的同名文本骗过去(例如WHERE note = 'from 测试表'会被误判为引用了测试表表)。对应 #6496:MySQL 下数据库/表/字段名为中文(非 ASCII)且不带反引号时,
WHERE后按空格或 Tab 补全不提示字段——表引用提取的正则只支持 ASCII 无引号标识符,中文库名/表名匹配不上。修复
tokenizeSqlSemantic方言感知词法器先屏蔽字符串字面量和注释,再扫描关键字,而不是在原始文本上盲扫。"..."在 MySQL 系方言下到底算字符串还是标识符取决于运行时sql_mode,代码本身不追踪这个设置,因此不用一个全局开关强行二选一,而是按语法位置消歧:只在紧跟比较运算符(=/</<>等)之后才当字符串屏蔽,其余位置(含紧跟 FROM/JOIN 之后)保留为标识符,修复了FROM "orders"这类双引号表名无法补全的问题。#/--注释处理、反引号/方括号标识符内含特殊字符导致扫描错位等一系列相关问题(按方言区分是否识别反斜杠转义,避免误伤 Postgres 等本身不支持反斜杠转义的方言)。验证
pnpm exec vitest run apps/desktop/src/lib/__tests__/sql/(1034 个测试全部通过,含新增/更新的用例)pnpm exec tsc --noEmit -p apps/desktop/tsconfig.jsonpnpm exec vitest run apps/desktop/src(全仓库 7221 个测试全部通过)#/--注释、反引号/方括号内含特殊字符等)逐条手工复现确认,而非仅依赖测试通过Fixes #6496