Skip to content

Add quote-to-bracket escaping for calculated measures with multiple ad-hoc measures - #50

Open
AlisonNeedsCopilot wants to merge 3 commits into
dodopizza:mainfrom
AlisonNeedsCopilot:Users/AlisonNeedsCopilot/quote-escape-bracket-multiple-ad-hoc-measures-calculated-measures
Open

Add quote-to-bracket escaping for calculated measures with multiple ad-hoc measures#50
AlisonNeedsCopilot wants to merge 3 commits into
dodopizza:mainfrom
AlisonNeedsCopilot:Users/AlisonNeedsCopilot/quote-escape-bracket-multiple-ad-hoc-measures-calculated-measures

Conversation

@AlisonNeedsCopilot

@AlisonNeedsCopilot AlisonNeedsCopilot commented Feb 10, 2026

Copy link
Copy Markdown

Merge After This PR: #48

Summary

This PR enhances the KQL dialect to properly handle calculated measures that contain multiple quoted column references combined with arithmetic operators or constants.

Problem

Previously, expressions like "Measure 1" + "Measure 2" or "Measure 1" * 2 were not properly escaped. The quote-stripping logic would incorrectly process these expressions, breaking the KQL output. Also, measures containing operators in their names were incorrectly parsed

Technical Details

  • _find_operator_outside_quotes() added to find operators not part of the measure name
  • Call _escape_and_quote_columns() recursively on the left and right side of the operator. This ensures all parts of the expression get bracketed and quoted. Exclude numbers from getting quoted
  • Update _is_number_literal() to include decimals that start and end with a number

UI Changes

Before:
image
After:
image

Alison Gim added 3 commits February 5, 2026 20:49
This ensures that extend operations (which may reference summarized columns)
are executed after the summarize clause in the generated KQL query.
…d hoc measures

- Add _find_operator_outside_quotes() helper to find operators not inside quoted strings
- Update _escape_and_quote_columns() to recursively escape both sides of operators
- Update _is_number_literal() to match integers and decimals with digits on both sides
- Add tests for:
  - Two quoted measures: "Measure 1" + "Measure 2" -> ["Measure 1"] + ["Measure 2"]
  - Measure with constant: "Measure 1" * 2 -> ["Measure 1"] * 2
  - Measure with operator in name: "Measure 1-2" -> ["Measure 1-2"]
  - _is_number_literal function validation

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 enhances the Kusto KQL SQLAlchemy compiler to correctly escape/convert calculated measure expressions that combine multiple quoted measure references and arithmetic (e.g., "Measure 1" + "Measure 2", "Measure 1" * 2) into the dialect’s bracket-escaped form (e.g., ["Measure 1"] + ["Measure 2"]). It also updates query compilation ordering to ensure | summarize precedes | extend, which is required for calculated measures referencing summarized outputs (noted as dependent on PR #48).

Changes:

  • Add _find_operator_outside_quotes() and update _escape_and_quote_columns() to recursively escape both sides of arithmetic operators outside quotes.
  • Update _is_number_literal() to recognize decimals with digits on both sides of the decimal point.
  • Expand/adjust unit tests for calculated measures and update expectations for summarize/extend ordering.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
sqlalchemy_kusto/dialect_kql.py Implements recursive escaping for arithmetic expressions and enforces summarize before extend in compiled KQL.
tests/unit/test_dialect_kql.py Updates expected compiled KQL ordering and adds coverage for multi-measure calculated expressions and numeric literal detection.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +145 to 156
# Add summarize first if it exists
if "summarize" in projections_parts_dict:
compiled_query_lines.append(projections_parts_dict.pop("summarize"))

# Then add extend after summarize
if "extend" in projections_parts_dict:
compiled_query_lines.append(projections_parts_dict.pop("extend"))

# Add remaining parts (project, sort)
for statement_part in projections_parts_dict.values():
if statement_part:
compiled_query_lines.append(statement_part)

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

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

projections_parts_dict always contains the keys summarize and extend (returned unconditionally by _get_projection_or_summarize), so the current if "summarize" in projections_parts_dict: / if "extend" in projections_parts_dict: checks are always true and will pop/append empty strings. Consider checking the value (projections_parts_dict.get("summarize")) or explicitly appending summarize/extend only when non-empty to avoid confusing control flow.

Copilot uses AI. Check for mistakes.
Comment on lines +145 to 154
# Add summarize first if it exists
if "summarize" in projections_parts_dict:
compiled_query_lines.append(projections_parts_dict.pop("summarize"))

# Then add extend after summarize
if "extend" in projections_parts_dict:
compiled_query_lines.append(projections_parts_dict.pop("extend"))

# Add remaining parts (project, sort)
for statement_part in projections_parts_dict.values():

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

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

The PR description indicates this should be merged after PR #48, but this diff still includes the summarize/extend reordering change. To keep this PR focused on quote-to-bracket escaping (and reduce merge conflicts), please rebase onto the branch that includes #48 so those unrelated changes drop out of this PR.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Will do once the other PR merges in

ag-ramachandran pushed a commit to ag-ramachandran/sqlalchemy-kusto that referenced this pull request Mar 25, 2026
…ntermediary measures, and parentheses support for calculated measures
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