2525_IFNULL_RE = re .compile (r"IFNULL\s*\([^,]+,\s*0\)" , re .IGNORECASE )
2626_SELECT_WRAP_RE = re .compile (r"^\s*\(\s*SELECT\s*\{([^}]+)\}\s*\)\s*$" , re .IGNORECASE )
2727_INNER_SELECT_RE = re .compile (r"\(\s*SELECT\s*\{([^}]+)\}\s*\)" , re .IGNORECASE )
28+ # Matches whichever comes first: a {type/id} identifier reference or a quoted string
29+ # literal -- both are case-sensitive data and must survive casefolding untouched.
30+ # Everything else in MAQL (keywords, operators, numbers, punctuation) carries no
31+ # case-sensitive meaning, per the MAQL reference (SELECT/BY/WHERE/FOR PREVIOUS/etc.
32+ # are case-insensitive; only {..} identifiers and quoted literal values are not).
33+ _PROTECTED_RE = re .compile (r"\{[^}]*\}|\"[^\"]*\"|'[^']*'" )
2834
2935
3036def _strip_outer_parens (s : str ) -> str :
@@ -42,8 +48,21 @@ def _strip_outer_parens(s: str) -> str:
4248 return s [1 :- 1 ].strip ()
4349
4450
51+ def _casefold_outside_protected (s : str ) -> str :
52+ """Lowercase MAQL keywords/operators while preserving case-sensitive {type/id}
53+ identifiers and quoted string literal values (e.g. WHERE {label/x} = "Active")."""
54+ parts = []
55+ last = 0
56+ for m in _PROTECTED_RE .finditer (s ):
57+ parts .append (s [last : m .start ()].lower ())
58+ parts .append (m .group (0 ))
59+ last = m .end ()
60+ parts .append (s [last :].lower ())
61+ return "" .join (parts )
62+
63+
4564def _normalize_maql (maql : str ) -> str :
46- """Semantic normalisation: strip whitespace, unwrap IFNULL/SELECT wrappers."""
65+ """Semantic normalisation: strip whitespace, unwrap IFNULL/SELECT wrappers, casefold keywords ."""
4766 if not maql :
4867 return ""
4968 m = maql .strip ()
@@ -56,7 +75,7 @@ def _normalize_maql(maql: str) -> str:
5675 m = re .sub (r"\{\s+" , "{" , m )
5776 m = re .sub (r"\s+\}" , "}" , m )
5877 m = re .sub (r"\s+" , " " , m )
59- return m .strip ()
78+ return _casefold_outside_protected ( m .strip () )
6079
6180
6281def _best_maql_match (actual_maql : str , expected_outputs : list [dict ]) -> tuple [bool , str ]:
0 commit comments