Skip to content

fix(lexer): don't treat backslash as string escape in SQL Server and Oracle - #103

Merged
databergs merged 2 commits into
mainfrom
austin.bergstrom/SDBM-2792-escape
Jul 14, 2026
Merged

fix(lexer): don't treat backslash as string escape in SQL Server and Oracle#103
databergs merged 2 commits into
mainfrom
austin.bergstrom/SDBM-2792-escape

Conversation

@databergs

@databergs databergs commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

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 in scanStringWithDelimiter.

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)

-- input (two ESCAPE N'\' literals with SQL between and after)
... WHERE col LIKE @p1 ESCAPE N'\' AND col LIKE @p2 ESCAPE N'\';

-- before (truncated — the first '\' escapes its closing quote and swallows
-- forward to the second literal's opening quote)
... WHERE col LIKE @p1 ESCAPE N?\?

-- after
... WHERE col LIKE @p1 ESCAPE N? AND col LIKE @p2 ESCAPE N?;

The fix

Disable backslash-escaping for the two ANSI-flavored dialects:

backslashEscapes := s.config.DBMS != DBMSSQLServer && s.config.DBMS != DBMSOracle
...
if backslashEscapes && ch == '\\' { ... }

For SQL Server and Oracle, '\' now terminates correctly whether or not more SQL follows.

Dialect coverage

DBMS Backslash escapes in '...'? Behavior
SQL Server No (quotes doubled) fixed
Oracle No (quotes doubled) fixed
MySQL Yes (default) unchanged — real MySQL semantics
Snowflake Yes unchanged
PostgreSQL No for ordinary '...', but yes for E'...' left as-is (see below)
default (no dbms) generic fallback unchanged

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.

  • Tokenizer specsTestLexer/sqlserver_escape_backslash_mid-query and .../oracle_escape_backslash_mid-query pin '\' as its own STRING token when more SQL follows.
  • Obfuscator regressions — SQL Server (DECLARE ... N'...' with two ESCAPE N'\' literals) and Oracle (two ESCAPE '\' 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.
  • Boundary guards — MySQL and Snowflake obfuscator tests assert they still absorb the trailing text (i.e. backslash-escaping stays on), proving the fix didn't over-broaden.
  • All specs were written failing first; the full suite passes after the fix.

Scope / follow-ups

  • PostgreSQL shares the same latent bug for ordinary strings, but its E'...' escape-string syntax does use backslash. The lexer tokenizes the E prefix 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.
  • Doubled-quote ('') escaping is still not handled for any dialect — separate issue.
  • Downstream: the production SQL Server / Oracle DBM paths must invoke the obfuscator with the matching dialect for customers to see the fix.

🤖 Generated with Claude Code

@databergs
databergs requested a review from a team as a code owner July 14, 2026 17:19

@datadog-official datadog-official Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Datadog Autotest: PASS

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.

Was this helpful? React 👍 or 👎

📊 Validated against 15 scenarios · Open Bits AI session

🤖 Datadog Autotest · Commit 5abf8da · What is Autotest? · Any feedback? Reach out in #autotest

@databergs databergs changed the title fix(lexer): don't treat backslash as string escape in SQL Server fix(lexer): don't treat backslash as string escape in SQL Server and Oracle Jul 14, 2026
…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>
@databergs
databergs force-pushed the austin.bergstrom/SDBM-2792-escape branch from 2d4ea77 to cc633a4 Compare July 14, 2026 17:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 scanStringWithDelimiter to 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.

@databergs
databergs merged commit 5fd41cb into main Jul 14, 2026
8 checks passed
@databergs
databergs deleted the austin.bergstrom/SDBM-2792-escape branch July 14, 2026 21:02
gh-worker-dd-mergequeue-cf854d Bot pushed a commit to DataDog/datadog-agent that referenced this pull request Jul 16, 2026
### 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants