Fix support for calculated measures in KQL compiler - #47
Open
AlisonNeedsCopilot wants to merge 10 commits into
Open
Fix support for calculated measures in KQL compiler#47AlisonNeedsCopilot wants to merge 10 commits into
AlisonNeedsCopilot wants to merge 10 commits into
Conversation
added 9 commits
January 28, 2026 21:41
- Add _find_top_level_operator helper to find operators outside quotes/brackets - Add _count_outer_parens to handle parenthesized expressions - Add _has_operators_outside_quotes to detect calculated measures - Update _escape_and_quote_columns to recursively handle arithmetic expressions - Update _get_projection_or_summarize to extract inline aggregates and build extend statements - Ensure summarize comes before extend in query output
- Test multi-aggregate expressions with inline COUNT functions - Test arithmetic expressions with column references - Test _escape_and_quote_columns with arithmetic operators - Test _escape_and_quote_columns with parenthesized expressions - Test _has_operators_outside_quotes helper - Test _count_outer_parens helper
- Test predefined measures compile to lowercase KQL functions - Test simple measure references with bracket notation - Test single and double parentheses preservation - Test multiplication by constants - Test addition of measure references - Test parenthesized additions - Test complex expressions with nested parens - Test measure plus constant - Test no double bracketing - Test standalone quoted identifiers and expressions
- Format code with black - Move func import to top-level in tests - Fix lambda binding issue (B023) - Split compound assertion (PT018) - Add noqa for acceptable magic numbers (PLR2004)
- Replace lambda with default argument with a typed nested function - Mypy can now infer the type of the re.Match parameter
- Restore original extend/summarize order (extend first) - Restore original _extract_maybe_agg_column_parts behavior (passthrough for known aggregates) - Restore original extend condition comparison (use raw column_name) - Remove paren-wrapping for calculated measures (was breaking existing tests) - All 112 unit tests now pass
This reverts commit caa5291.
- Summarize must come before extend so calculated measures can reference aggregates - Restore original _extract_maybe_agg_column_parts behavior (AGGREGATE_PATTERN first) - Update test expectations for new statement order
- Fix KQL compiler to support calculated measures that reference aggregates - Update tests to use SQLAlchemy 2.0 compatible select() syntax - Remove deprecated select([...]) list wrapper and from_obj/columns kwargs - Add 18 new unit tests for calculated measures functionality
There was a problem hiding this comment.
Pull request overview
This PR fixes critical issues in the KQL (Kusto Query Language) SQLAlchemy dialect to properly support calculated measures - arithmetic expressions that combine multiple aggregates. The main issues addressed are incorrect clause ordering (extend before summarize), missing parentheses preservation, quoted identifier conversion, and uppercase function names.
Changes:
- Added helper functions for parsing arithmetic expressions and detecting operators outside quotes/brackets
- Fixed clause ordering to ensure
summarizeappears beforeextendin generated KQL - Enhanced aggregate detection and column escaping to handle calculated measures with arithmetic operators
- Improved multi-argument KQL function support (percentile, dcountif, etc.)
- Modernized test syntax from SQLAlchemy 1.x to 2.x patterns
- Added comprehensive test suite (112 tests) covering calculated measures functionality
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| sqlalchemy_kusto/dialect_kql.py | Core implementation of calculated measures support including new helper functions _find_top_level_operator, _count_outer_parens, _has_operators_outside_quotes, and updates to query compilation logic |
| tests/unit/test_dialect_kql.py | Test modernization (SQLAlchemy 2.x syntax) and new TestCalculatedMeasures class with comprehensive tests for arithmetic expressions, parentheses handling, and quoted identifiers |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…lass - Created _ParseState class to centralize state tracking logic - Refactored _find_top_level_operator, _find_matching_paren, and _is_inside_quotes_or_brackets - Reduced code duplication and improved maintainability - All 138 unit tests passing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR: Fix KQL Dialect for Calculated Measures Support
Overview
The KQL (Kusto Query Language) SQLAlchemy dialect in
sqlalchemy-kustodoes not properly handle calculated measures. When users create calculated measures that combine multiple aggregates with arithmetic operators (e.g.,"Measure 1" + "Measure 2"), the generated KQL is malformed and fails to execute.Issues fixed:
(("Measure 1"))were losing their parentheses, breaking operator precedence"column"wasn't being converted to KQL-style["column"]count()notCOUNT())extendappeared beforesummarize, causing reference errors sinceextendcolumns reference values created insummarizeAdditional improvements:
percentile(col, 99)anddcountif(col, predicate)Technical Details
New Functions Added
_find_top_level_operator(text, operator)(A + B) * C._count_outer_parens(text)(count, stripped_text). Used to preserve parentheses for operator precedence in calculated measures._has_operators_outside_quotes(expr)+,-,*,/) outside of quoted strings and brackets. Used to detect calculated measures vs simple aggregates.Functions Updated
visit_select()summarizeclause is added beforeextendclause. This is required becauseextendcolumns reference values created insummarize._get_projection_or_summarize()_has_operators_outside_quotes(). Added inline aggregate extraction tosummarizefor expressions containing aggregates. Added column reference wrapping in parentheses for arithmetic precedence._extract_maybe_agg_column_parts()AGGREGATE_PATTERNfirst (handles SQL-style aggregates includingcount(distinct X)). Added support for multi-arg KQL aggregates likepercentile(col, 99)anddcountif(col, predicate)- now properly escapes the first (column) argument while leaving predicates/numeric args untouched._escape_and_quote_columns()_find_top_level_operator(). Preserves parentheses count using_count_outer_parens(). Properly processes nested expressions like(A + B) * Cby recursively escaping each operand._convert_quoted_columns()"col") to bracket notation (["col"]), not just those inside function calls._sql_to_kql_aggregate()"*" in sql_aggto"*" in str(column_name)for proper star detection. Added null check forextra_paramsto preventNonefrom being concatenated.Example
Input SQL expression:
Before (broken KQL):
After (correct KQL):
UI Change
Testing
percentile,dcountif,countif, etc.)