fix(gooddata-eval): make MAQL comparison case-insensitive for keywords - #1719
fix(gooddata-eval): make MAQL comparison case-insensitive for keywords#1719Tomkess wants to merge 1 commit into
Conversation
_normalize_maql/_best_maql_match compare an agent's generated MAQL against expected_output.maql via exact string equality after whitespace/wrapper normalization -- but MAQL keywords (SELECT, FOR PREVIOUS, WHERE, BY, ...) are case-insensitive at the query-engine level (confirmed against the MAQL reference), while the comparison itself was fully case-sensitive. Reproduced live in gdc-mic-ai-evaluation, post the #1718 fix: fixture "Create a metric for the prior-year value of Active cards" expects SELECT {metric/active_card_count_-_txn_-_cutcgco} FOR Previous({label/process_date.year}) Agent produced, verbatim: SELECT {metric/active_card_count_-_txn_-_cutcgco} FOR PREVIOUS({label/process_date.year}) Byte-identical except FOR PREVIOUS vs FOR Previous -- scored as a fail. First fix attempt considered and rejected: lowercase everything outside {type/id} braces. That's wrong -- WHERE-clause literal values are ALSO outside braces (e.g. WHERE {label/status} = "Active") and are real, case-sensitive data, not keywords; blindly folding them would create a new false-positive risk (two genuinely different filter values scored as equal). Actual fix: per the MAQL reference, every literal value is quoted and every identifier lives inside {..} -- both are exhaustively structural markers, so protecting text inside either while casefolding everything else needs no keyword list at all (which would risk being incomplete against MAQL's large vocabulary: SELECT, BY, WHERE, HAVING, FOR PREVIOUS/NEXT/EACH, WITHOUT PF, TOP/BOTTOM, WITHIN, RANK family, RUNSUM family, IFNULL, CASE/WHEN, 15+ math functions, ...). Added _casefold_outside_protected(), applied as the final step in _normalize_maql. Tests added: - keyword case-insensitivity on the exact reproduced case (FOR PREVIOUS vs FOR Previous) - identifier case preserved ({metric/Mixed_Case_Id} untouched) - quoted literal case preserved AND still distinguishes real differences (WHERE x = "Active" vs WHERE x = "active" must stay a genuine mismatch -- this is the test that would have caught the rejected first draft) Updated the one existing test whose expected value assumed no case normalization ever happens (SELECT -> select). Full gooddata-eval suite: 274 passed, 9 pre-existing unrelated failures (missing openai extra in this test env; two unrelated test files) -- identical count to before this change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 13 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary
_normalize_maql/_best_maql_match(metric_skill.py) compare an agent'sgenerated MAQL against
expected_output.maqlvia exact string equality afterwhitespace/wrapper normalization — but MAQL keywords (
SELECT,FOR PREVIOUS,WHERE,BY, ...) are case-insensitive at the query-engine level,while the comparison was fully case-sensitive.
Reproduced live
In
gdc-mic-ai-evaluation, after #1718 landed: fixture "Create a metric forthe prior-year value of Active cards" expects
Agent produced, verbatim:
Byte-identical except
FOR PREVIOUSvsFOR Previous— scored as a fail.First approach considered and rejected
Lowercase everything outside
{type/id}braces. Wrong —WHERE-clauseliteral values are also outside braces (e.g.
WHERE {label/status} = "Active") and are real, case-sensitive data, not keywords. Blindly foldingthem creates a new false-positive risk: two genuinely different filter
values would be scored as equal.
Actual fix
Per the MAQL reference,
every literal value in MAQL is quoted, and every identifier lives inside
{..}— both are exhaustive, structural markers. Protecting text insideeither while casefolding everything else needs no keyword list at all,
which matters because MAQL's actual keyword vocabulary is large (
SELECT,BY,WHERE,HAVING,FOR PREVIOUS/NEXT/EACH,WITHOUT PF,TOP/BOTTOM,WITHIN, theRANKfamily, theRUNSUMfamily,IFNULL,CASE/WHEN, 15+ math functions, ...) — an enumerated list would inevitablymiss one and only partially fix the bug.
Applied as the final step in
_normalize_maql, after the existingwhitespace/wrapper normalization.
Test plan
test_normalize_maql_is_case_insensitive_for_keywords— the exactreproduced case (
FOR PREVIOUSvsFOR Previous) now normalizes equal.test_normalize_maql_preserves_identifier_case—{metric/Mixed_Case_Id}survives untouched.
test_normalize_maql_preserves_quoted_literal_case— the test thatwould have caught the rejected first draft:
WHERE x = "Active"vsWHERE x = "active"must stay a genuine mismatch, not a false positive.test_normalize_maql_strips_whitespace, whose expected valueassumed no case normalization ever happens.
gooddata-evalsuite: 274 passed, 9 pre-existing unrelatedfailures (missing
openaiextra in this test env; two unrelated testfiles) — identical count to before this change.
ruff check/ruff format --checkclean.Related
Found while re-testing #1718's fix against real production fixtures — see
that PR's description for the broader investigation this follows from.