fix(lexer): don't treat backslash as string escape in SQL Server and Oracle - #103
Conversation
There was a problem hiding this comment.
More details
The PR correctly fixes a SQL Server string-parsing bug where backslash was unconditionally treated as an escape character. Since SQL Server doesn't use backslash for escaping (it uses quote-doubling), strings like ESCAPE '\' were incorrectly treated as having an escaped closing quote, causing the lexer to swallow the rest of the query. The fix gates backslash-escape behavior to non-SQL-Server dialects, restoring correct parsing for SQL Server while preserving MySQL and default dialect behavior. All three test suites pass, and edge cases validate correctly.
📊 Validated against 15 scenarios · Open Bits AI session
🤖 Datadog Autotest · Commit 5abf8da · What is Autotest? · Any feedback? Reach out in #autotest
…Oracle
SQL Server (T-SQL) and Oracle follow ANSI-SQL string semantics: backslash
is an ordinary character and a quote inside a string literal is escaped by
doubling it (''). The lexer, however, unconditionally treated '\' as an
escape character in scanStringWithDelimiter. As a result a literal like
ESCAPE '\' (contents: a single backslash) had its closing quote treated as
escaped, so scanning continued and swallowed the rest of the batch up to
the next quote. With two such literals the text between them collapsed into
one string, truncating the obfuscated query customers saw in DBM
(SDBM-2792). A previous special case only rescued this when the query ended
with '\'; the bug remained whenever more SQL followed.
Disable backslash-escaping for the two ANSI-flavored dialects. MySQL and
Snowflake genuinely use C-style backslash escaping and are left unchanged.
PostgreSQL ordinary strings also do not backslash-escape, but its E'...'
escape-string syntax does and the lexer cannot yet distinguish the two, so
Postgres is deferred to a follow-up.
Tests (all synthetic queries, no customer data):
- Tokenizer specs pinning '\' as its own STRING token mid-query for
SQL Server and Oracle.
- Obfuscator regressions using the two-literal shape (SQL to both between
and after the escapes) for SQL Server and Oracle.
- Boundary guards asserting MySQL and Snowflake still escape, proving the
fix did not over-broaden.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2d4ea77 to
cc633a4
Compare
There was a problem hiding this comment.
Pull request overview
This PR fixes dialect-specific string scanning so the lexer does not treat \ as a string escape character for SQL Server (T-SQL) and Oracle, preventing unterminated-string behavior that could swallow subsequent SQL and lead to truncated/over-obfuscated output.
Changes:
- Update
scanStringWithDelimiterto gate backslash-escape handling behind a dialect check (disabled for SQL Server and Oracle). - Add lexer specs ensuring
ESCAPE '\'terminates correctly mid-query for SQL Server and Oracle. - Add obfuscator regression tests for SQL Server/Oracle and “boundary guard” tests for MySQL/Snowflake to confirm their behavior remains unchanged.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| sqllexer.go | Disables backslash-escape handling for SQL Server and Oracle during string scanning. |
| sqllexer_test.go | Adds tokenizer tests that reproduce the mid-query ESCAPE '\' termination case for SQL Server and Oracle. |
| obfuscator_test.go | Adds regressions verifying obfuscation no longer truncates SQL Server/Oracle queries and remains unchanged for MySQL/Snowflake. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
### What does this PR do? Bumps `github.com/DataDog/go-sqllexer` from v0.2.3 to v0.2.4 across all modules that depend on it. ### Motivation Picks up [DataDog/go-sqllexer#103](DataDog/go-sqllexer#103), which stops treating backslash as a string escape character in SQL Server and Oracle string literals. Previously, a literal like `ESCAPE '\'` had its closing quote misread as escaped, causing the obfuscator to swallow SQL up to the next quote and truncate the obfuscated query shown in DBM. ### Describe how you validated your changes Bumped the version in `pkg/obfuscate/go.mod` and ran `dda inv tidy-all` (`go work sync` + `go mod tidy` across all modules). The diff is limited to the go-sqllexer version and its `go.sum` hashes. Co-authored-by: eric-weaver <eweaver755@gmail.com> Co-authored-by: jason.petersen <jason.petersen@datadoghq.com>
Summary
SQL Server (T-SQL) and Oracle both follow ANSI-SQL string semantics: backslash is an ordinary character, and a quote inside a string literal is escaped by doubling it (
''). The lexer, however, unconditionally treated\as an escape character inscanStringWithDelimiter.As a result, a literal like
ESCAPE '\'(whose contents are a single backslash) had its closing quote treated as escaped, so scanning kept going and swallowed the rest of the batch up to the next quote. Everything in between collapsed into one string literal → obfuscated to a single?, and the query text appeared truncated in DBM.Reported for SQL Server in SDBM-2792; Oracle has the same root cause and is fixed here too. A previous "special case" only rescued this when the query ended with
'\'; the bug remained whenever more SQL followed.Before / after (mssql)
The fix
Disable backslash-escaping for the two ANSI-flavored dialects:
For SQL Server and Oracle,
'\'now terminates correctly whether or not more SQL follows.Dialect coverage
'...'?'...', but yes forE'...'MySQL and Snowflake genuinely use C-style backslash escaping, so disabling it there would introduce a bug — hence they're deliberately untouched.
Tests
All test queries are synthetic (
col/tbl/%foo%/%bar%); they reproduce the problematic shape without any data from the ticket.TestLexer/sqlserver_escape_backslash_mid-queryand.../oracle_escape_backslash_mid-querypin'\'as its ownSTRINGtoken when more SQL follows.DECLARE ... N'...'with twoESCAPE N'\'literals) and Oracle (twoESCAPE '\'literals) with SQL both between and after the escapes. The two-literal shape is the one that produced the truncation (the first'\'would otherwise consume forward to the opening quote of the second literal), so it is covered explicitly on both dialect paths.Scope / follow-ups
E'...'escape-string syntax does use backslash. The lexer tokenizes theEprefix as a separate token and cannot yet tell an escape string from an ordinary one, so a correct Postgres fix needs that distinction first — deferred to a follow-up.'') escaping is still not handled for any dialect — separate issue.🤖 Generated with Claude Code