Skip to content

Keep string literals byte-exact in the SQL console parser - #58

Merged
oblomov-dev merged 1 commit into
mainfrom
claude/sql-case-literals
Nov 13, 2026
Merged

oblomov-dev merged 1 commit into
mainfrom
claude/sql-case-literals

Conversation

@oblomov-dev

Copy link
Copy Markdown
Member

What was wrong

z2ui5_cl_sql_query upper-cased the whole select list, FROM, GROUP BY and ORDER BY text, so a literal inside an expression changed its content: SELECT CASE WHEN carrid = 'lh' THEN 'Lufthansa' ELSE 'other' END AS name FROM scarr ran as 'LH' / 'LUFTHANSA' / 'OTHER'. That changed both the comparison and the output.

The same parser also damaged literals in two more ways:

  • condense( ) in normalize( ) and on every clause collapsed blanks inside literals. WHERE carrname = 'Air Berlin' lost a blank.
  • The select-list tokenizer split at blanks and commas inside literals. 'a, b' became 'a b', and a literal 'end', once upper-cased, closed a CASE early.

A plain WHERE literal (WHERE carrname = 'Lufthansa') was not affected before, because WHERE is never upper-cased. Only blanks inside its literals were collapsed.

The fix

  • split_literals( ) splits SQL text into literals and the text between them. It handles '...' and `...`, and a doubled delimiter counts as an escaped one.
  • upper_outside_literals( ), condense_outside_literals( ) and tokenize( ) build on it. They replace to_upper( ), condense( ) and the replace-commas-and-split tokenizing in normalize, parse_segment, get_items, get_sources, join_by_comma and join_order_by.
  • Identifiers and keywords are still upper-cased.

Not part of this change: the keyword search (FROM, WHERE, ORDER BY, UNION SELECT, INTO, UP TO n ROWS) still runs on the raw text. A literal that contains such a keyword with blanks around it can still split the query in the wrong place.

Tests

There are five new tests in ltcl_parser. All five fail on the old code and pass now:

  • parse_case_literals: the CASE literal example
  • parse_where_literal: a plain WHERE literal (this assertion already passed before) and a literal with inner blanks
  • parse_escaped_quote: 'o''neil', 'it''s me' and 'O''Neil'
  • parse_mixed_case_identifiers: mixed-case keywords and identifiers are still upper-cased in every clause
  • target_case_literal_comma: a comma, a double blank and 'end' inside literals, through build_target. The test class is now LOCAL FRIENDS.

npm run check passes, npm run unit reports 20 passed, and the 7.02 auto_downport lint is clean.


Generated by Claude Code

The parser upper-cased the whole select list, FROM, GROUP BY and ORDER BY
text, so a literal inside an expression changed its content:
CASE WHEN carrid = 'lh' THEN 'Lufthansa' END became 'LH' / 'LUFTHANSA',
which changed both the comparison and the output. condense( ) in
normalize( ) and on every clause also collapsed blanks inside literals
(WHERE carrname = 'Air  Berlin' lost a blank), and the select-list
tokenizer split at blanks and commas inside literals ('a, b' became
'a b', a literal 'END' ended a CASE early).

split_literals( ) now separates the text into literals ('...' and `...`,
a doubled delimiter being an escaped one) and the text between them.
upper_outside_literals( ), condense_outside_literals( ) and tokenize( )
build on it and replace to_upper( ), condense( ) and the
replace-commas-and-split tokenizing in the parser. Identifiers and
keywords are still upper-cased. A plain WHERE literal was not affected
before (WHERE is never upper-cased), only its inner blanks were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014YZFmF4CrXfWRsmrwPJTRL
@oblomov-dev
oblomov-dev merged commit 545dd16 into main Nov 13, 2026
9 checks passed
@oblomov-dev
oblomov-dev deleted the claude/sql-case-literals branch November 13, 2026 09:31
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.

2 participants