Skip to content

Fix build_chart_query producing empty SELECT when pagination is enabled - #1442

Open
siddhant3030 wants to merge 6 commits into
mainfrom
fix/chart-pagination-empty-select
Open

Fix build_chart_query producing empty SELECT when pagination is enabled#1442
siddhant3030 wants to merge 6 commits into
mainfrom
fix/chart-pagination-empty-select

Conversation

@siddhant3030

@siddhant3030 siddhant3030 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Problem

Any non-table chart (bar, line, pie) with pagination.enabled: true in its config renders garbage: every category shows as "Unknown" and metric values are lost.

Example: a bar chart of COUNT_DISTINCT(child_id) by partner_name with page size 20 returns xAxisData: ["Unknown", "Unknown", ...] — 20 bars, all labeled Unknown.

Root cause

In build_chart_query (ddpui/core/charts/charts_service.py), all column-selection logic — dimension columns, metric aggregates, and GROUP BY, for every chart type — was nested inside the else: # No pagination branch. With pagination enabled the function returned a query with an empty SELECT list:

SELECT
FROM (SELECT * FROM prod_analytics.some_view LIMIT 20) AS paginated_data

Postgres accepts this and returns N column-less rows. The transform step then reads the missing dimension key as None on every row and maps each one to the "Unknown" null label — one "Unknown" bar per raw row, since grouping never happened.

Fix

De-indent the column/aggregation/GROUP BY logic out of the else branch so it runs on both the paginated and non-paginated paths. With the fix, the same payload generates:

SELECT partner_name, count(distinct(child_id)) AS "COUNT_DISTINCT(child_id)"
FROM (SELECT * FROM prod_analytics.some_view LIMIT 20) AS paginated_data
GROUP BY partner_name

The pivot-table early-return path (which bypasses generic pagination) is preserved.

Verification

  • Compiled SQL for the failing payload verified before/after (empty SELECT → correct SELECT + GROUP BY).
  • Chart unit tests pass: ddpui/tests/core/charts/, test_pivot_query_builder.py, test_pivot_transform.py (109 passed; the django_db-marked tests error locally for lack of a test DB and will run in CI).

Notes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved pagination for bar, line, pie, and other chart queries.
    • Ensured paginated results retain selected dimensions, metrics, filters, grouping, sorting, and page-size settings.
    • Preserved expected behavior when pagination is disabled.
    • Fixed query handling so table and pivot views continue to return complete, correctly filtered results without unintended pagination.

Move all column-selection logic (for number, pie, bar, line, table,
pivot_table chart types) out of the 'else: # No pagination' branch so
it executes regardless of whether pagination is enabled.

Previously, when limit was not None the if-branch only set up the
paginated subquery and outer query_builder, but the chart-type
column-building code was entirely inside the else block and was
skipped. This resulted in a SELECT with no columns, causing
'Available keys: []' errors on chart-data-preview requests with
pagination parameters.

Fixes DALGO-BACKEND-2BQ (https://dalgo.sentry.io/issues/113816406/)
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Chart query construction now shares chart-type handling across paginated and non-paginated queries. Tests cover pagination, SQL structure, page sizes, disabled pagination, and table or pivot exceptions.

Changes

Chart query flow

Layer / File(s) Summary
Shared pivot and table queries
ddpui/core/charts/charts_service.py
Pivot and table query construction, filtering, aggregation, sorting, and dimension selection now apply outside the pagination branch.
Shared chart-specific queries
ddpui/core/charts/charts_service.py
Number, pie, and other chart queries now apply metric, grouping, ordering, and validation logic to paginated queries.
Pagination regression coverage
ddpui/tests/core/charts/test_query_generation_multiple_dimensions.py
Tests verify paginated SQL, page sizes, grouping, unchanged non-paginated behavior, disabled pagination, and table or pivot exclusions. Database access markers now apply per test class.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: ishankoradia

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main fix: preventing an empty SELECT when pagination is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/chart-pagination-empty-select

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
ddpui/core/charts/charts_service.py (1)

591-613: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated metric-to-column logic into a helper.

The number branch (Lines 591-613) and the pie branch (Lines 639-661) contain identical metric handling: expression metrics, count-with-null-column aliasing, and aggregate column addition. build_pivot_table_query (Lines 465-472) implements a third variant of the same split. A shared helper keeps alias rules consistent when metric handling changes.

♻️ Proposed helper extraction
def add_metric_column(query_builder: AggQueryBuilder, metric) -> AggQueryBuilder:
    """Add one metric to the SELECT list as an expression or an aggregate column."""
    if metric.column_expression:
        alias = metric.alias or "expression_metric"
        query_builder.add_column(literal_column(metric.column_expression).label(alias))
        return query_builder

    if metric.aggregation and metric.aggregation.lower() == "count" and metric.column is None:
        alias = f"count_all_{metric.alias}" if metric.alias else "count_all"
    else:
        if not metric.column:
            raise ValueError(f"Column is required for {metric.aggregation} aggregation")
        alias = metric.alias or f"{metric.aggregation}_{metric.column}"

    query_builder.add_aggregate_column(metric.column, metric.aggregation, alias)
    return query_builder

Then both branches reduce to:

-        # Expression metric: inline raw SQL (e.g. "SUM(a)/SUM(b)") — no aggregation/column.
-        if metric.column_expression:
-            alias = metric.alias or "expression_metric"
-            query_builder.add_column(literal_column(metric.column_expression).label(alias))
-        else:
-            ...
+        query_builder = add_metric_column(query_builder, metric)

Also applies to: 639-661

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ddpui/core/charts/charts_service.py` around lines 591 - 613, Extract the
shared metric handling from the number and pie branches into an
`add_metric_column` helper that accepts an `AggQueryBuilder` and metric,
preserving expression metrics, count-with-null-column aliases, validation, and
aggregate-column addition. Replace both duplicated branches with calls to this
helper, and update the metric split in `build_pivot_table_query` to use it so
all metric alias rules remain consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ddpui/core/charts/charts_service.py`:
- Around line 682-684: Add a compiled-query test for a paginated non-table
aggregation path, using a bar, line, or pie chart with limit set. Exercise
build_chart_query followed by build_multi_metric_query and assert the generated
query contains the expected dimension, metrics, and GROUP BY clauses.

---

Nitpick comments:
In `@ddpui/core/charts/charts_service.py`:
- Around line 591-613: Extract the shared metric handling from the number and
pie branches into an `add_metric_column` helper that accepts an
`AggQueryBuilder` and metric, preserving expression metrics,
count-with-null-column aliases, validation, and aggregate-column addition.
Replace both duplicated branches with calls to this helper, and update the
metric split in `build_pivot_table_query` to use it so all metric alias rules
remain consistent.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fe1124a1-9418-4752-8eb1-16914bc0dd67

📥 Commits

Reviewing files that changed from the base of the PR and between d41c2ab and 85f074a.

📒 Files selected for processing (1)
  • ddpui/core/charts/charts_service.py

Comment thread ddpui/core/charts/charts_service.py
@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.83333% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.80%. Comparing base (c4fb29e) to head (1dd9194).

Files with missing lines Patch % Lines
ddpui/core/charts/charts_service.py 70.83% 21 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1442      +/-   ##
==========================================
+ Coverage   65.74%   65.80%   +0.05%     
==========================================
  Files         170      170              
  Lines       19658    19658              
==========================================
+ Hits        12925    12936      +11     
+ Misses       6733     6722      -11     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Compile-only tests (no DB) asserting that build_chart_query with
pagination enabled still selects the dimension and metric columns and
applies GROUP BY — the empty-SELECT failure mode this PR fixes. Also
pins the unpaginated path (no LIMIT subquery) and the table/pivot
exemptions. Verified to fail on main before the fix (4 failures) and
pass with it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ddpui/tests/unit/test_chart_pagination_query.py`:
- Around line 51-71: Update the pagination query tests around _compile and the
line/pie chart test methods to extract the outer SELECT list from the generated
SQL, then assert that partner_name and the DISTINCT metric expression appear in
that list. Replace whole-query assertions for these columns while retaining the
existing GROUP BY, LIMIT, and paginated_data checks.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e5d9fef3-b3ac-4297-a39e-b8522ad996bd

📥 Commits

Reviewing files that changed from the base of the PR and between 85f074a and 66f0e2e.

📒 Files selected for processing (1)
  • ddpui/tests/unit/test_chart_pagination_query.py

Comment on lines +51 to +71
assert "partner_name" in sql, f"dimension column missing from SELECT:\n{sql}"
assert "distinct" in sql.lower(), f"metric aggregate missing from SELECT:\n{sql}"
assert "GROUP BY" in sql_upper, f"GROUP BY missing:\n{sql}"
assert "LIMIT 20" in sql_upper, f"pagination LIMIT missing:\n{sql}"
assert "paginated_data" in sql, f"pagination subquery missing:\n{sql}"

def test_line_chart_with_pagination_selects_dimension_metric_and_groups(self):
sql = _compile(_paginated_payload("line"))

assert "partner_name" in sql
assert "distinct" in sql.lower()
assert "GROUP BY" in sql.upper()
assert "LIMIT 20" in sql.upper()

def test_pie_chart_with_pagination_selects_dimension_metric_and_groups(self):
sql = _compile(_paginated_payload("pie"))

assert "partner_name" in sql
assert "distinct" in sql.lower()
assert "GROUP BY" in sql.upper()
assert "LIMIT 20" in sql.upper()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert columns in the outer SELECT list.

Lines 51-71 search the complete SQL statement. A query can contain partner_name only in GROUP BY and still pass, although chart results would not contain the dimension value. Extract the outer SELECT list and assert both partner_name and DISTINCT there.

Proposed test fix
 def _compile(payload):
     qb = build_chart_query(payload, _org_warehouse())
     return str(qb.build().compile(compile_kwargs={"literal_binds": True}))

+def _outer_select_list(sql):
+    return sql.upper().split("FROM", 1)[0].replace("SELECT", "", 1).strip()
+
 ...
     def test_bar_chart_with_pagination_selects_dimension_metric_and_groups(self):
         sql = _compile(_paginated_payload("bar"))
         sql_upper = sql.upper()
+        select_list = _outer_select_list(sql)

-        assert "partner_name" in sql, f"dimension column missing from SELECT:\n{sql}"
-        assert "distinct" in sql.lower(), f"metric aggregate missing from SELECT:\n{sql}"
+        assert "PARTNER_NAME" in select_list, f"dimension column missing from SELECT:\n{sql}"
+        assert "DISTINCT" in select_list, f"metric aggregate missing from SELECT:\n{sql}"

Apply the same assertions to the line and pie tests.

Also applies to: 77-82

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ddpui/tests/unit/test_chart_pagination_query.py` around lines 51 - 71, Update
the pagination query tests around _compile and the line/pie chart test methods
to extract the outer SELECT list from the generated SQL, then assert that
partner_name and the DISTINCT metric expression appear in that list. Replace
whole-query assertions for these columns while retaining the existing GROUP BY,
LIMIT, and paginated_data checks.

Move the pagination tests from a standalone file into
test_query_generation_multiple_dimensions.py. The module-level django_db
marker becomes per-class markers on the existing classes so the new
pagination classes stay DB-free (they only compile SQL).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ddpui/tests/core/charts/test_query_generation_multiple_dimensions.py`:
- Around line 555-586: Update the pagination chart tests around _compile_sql and
the chart-specific test methods to extract the outer SELECT clause rather than
searching the full SQL string. Assert that this clause includes partner_name and
the expected metric alias for each bar, line, and pie chart, while retaining the
existing grouping and pagination assertions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4080abd3-6b14-4a8c-a656-7557594ff367

📥 Commits

Reviewing files that changed from the base of the PR and between 66f0e2e and bdb5574.

📒 Files selected for processing (1)
  • ddpui/tests/core/charts/test_query_generation_multiple_dimensions.py

Comment on lines +555 to +586
assert "partner_name" in sql, f"dimension column missing from SELECT:\n{sql}"
assert "distinct" in sql.lower(), f"metric aggregate missing from SELECT:\n{sql}"
assert "GROUP BY" in sql_upper, f"GROUP BY missing:\n{sql}"
assert "LIMIT 20" in sql_upper, f"pagination LIMIT missing:\n{sql}"
assert "paginated_data" in sql, f"pagination subquery missing:\n{sql}"

def test_line_chart_with_pagination_selects_dimension_metric_and_groups(self):
sql = _compile_sql(_paginated_payload("line"))

assert "partner_name" in sql
assert "distinct" in sql.lower()
assert "GROUP BY" in sql.upper()
assert "LIMIT 20" in sql.upper()

def test_pie_chart_with_pagination_selects_dimension_metric_and_groups(self):
sql = _compile_sql(_paginated_payload("pie"))

assert "partner_name" in sql
assert "distinct" in sql.lower()
assert "GROUP BY" in sql.upper()
assert "LIMIT 20" in sql.upper()

def test_pagination_respects_page_size(self):
sql = _compile_sql(_paginated_payload("bar", page_size=75))
assert "LIMIT 75" in sql.upper()

def test_select_list_is_never_empty(self):
"""The literal failure mode: 'SELECT FROM' with nothing between."""
for chart_type in ("bar", "line", "pie"):
sql = _compile_sql(_paginated_payload(chart_type))
select_clause = sql.upper().split("FROM")[0].replace("SELECT", "").strip()
assert select_clause, f"{chart_type}: empty SELECT list:\n{sql}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the outer SELECT list contains the dimension.

Lines 555-586 search the complete SQL string. A query that includes partner_name only in GROUP BY would pass these tests but still omit the chart category from the result rows.

Extract the outer SELECT clause. Assert that it contains partner_name and the metric alias for each chart type.

Proposed test change
     def test_select_list_is_never_empty(self):
         """The literal failure mode: 'SELECT FROM' with nothing between."""
         for chart_type in ("bar", "line", "pie"):
             sql = _compile_sql(_paginated_payload(chart_type))
-            select_clause = sql.upper().split("FROM")[0].replace("SELECT", "").strip()
+            select_clause = sql.upper().split("FROM", 1)[0]
+            assert "PARTNER_NAME" in select_clause, (
+                f"{chart_type}: dimension missing from outer SELECT:\n{sql}"
+            )
+            assert "CNT_CHILDREN" in select_clause, (
+                f"{chart_type}: metric missing from outer SELECT:\n{sql}"
+            )
+            select_clause = select_clause.replace("SELECT", "").strip()
             assert select_clause, f"{chart_type}: empty SELECT list:\n{sql}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert "partner_name" in sql, f"dimension column missing from SELECT:\n{sql}"
assert "distinct" in sql.lower(), f"metric aggregate missing from SELECT:\n{sql}"
assert "GROUP BY" in sql_upper, f"GROUP BY missing:\n{sql}"
assert "LIMIT 20" in sql_upper, f"pagination LIMIT missing:\n{sql}"
assert "paginated_data" in sql, f"pagination subquery missing:\n{sql}"
def test_line_chart_with_pagination_selects_dimension_metric_and_groups(self):
sql = _compile_sql(_paginated_payload("line"))
assert "partner_name" in sql
assert "distinct" in sql.lower()
assert "GROUP BY" in sql.upper()
assert "LIMIT 20" in sql.upper()
def test_pie_chart_with_pagination_selects_dimension_metric_and_groups(self):
sql = _compile_sql(_paginated_payload("pie"))
assert "partner_name" in sql
assert "distinct" in sql.lower()
assert "GROUP BY" in sql.upper()
assert "LIMIT 20" in sql.upper()
def test_pagination_respects_page_size(self):
sql = _compile_sql(_paginated_payload("bar", page_size=75))
assert "LIMIT 75" in sql.upper()
def test_select_list_is_never_empty(self):
"""The literal failure mode: 'SELECT FROM' with nothing between."""
for chart_type in ("bar", "line", "pie"):
sql = _compile_sql(_paginated_payload(chart_type))
select_clause = sql.upper().split("FROM")[0].replace("SELECT", "").strip()
assert select_clause, f"{chart_type}: empty SELECT list:\n{sql}"
assert "partner_name" in sql, f"dimension column missing from SELECT:\n{sql}"
assert "distinct" in sql.lower(), f"metric aggregate missing from SELECT:\n{sql}"
assert "GROUP BY" in sql_upper, f"GROUP BY missing:\n{sql}"
assert "LIMIT 20" in sql_upper, f"pagination LIMIT missing:\n{sql}"
assert "paginated_data" in sql, f"pagination subquery missing:\n{sql}"
def test_line_chart_with_pagination_selects_dimension_metric_and_groups(self):
sql = _compile_sql(_paginated_payload("line"))
assert "partner_name" in sql
assert "distinct" in sql.lower()
assert "GROUP BY" in sql.upper()
assert "LIMIT 20" in sql.upper()
def test_pie_chart_with_pagination_selects_dimension_metric_and_groups(self):
sql = _compile_sql(_paginated_payload("pie"))
assert "partner_name" in sql
assert "distinct" in sql.lower()
assert "GROUP BY" in sql.upper()
assert "LIMIT 20" in sql.upper()
def test_pagination_respects_page_size(self):
sql = _compile_sql(_paginated_payload("bar", page_size=75))
assert "LIMIT 75" in sql.upper()
def test_select_list_is_never_empty(self):
"""The literal failure mode: 'SELECT FROM' with nothing between."""
for chart_type in ("bar", "line", "pie"):
sql = _compile_sql(_paginated_payload(chart_type))
select_clause = sql.upper().split("FROM", 1)[0]
assert "PARTNER_NAME" in select_clause, (
f"{chart_type}: dimension missing from outer SELECT:\n{sql}"
)
assert "CNT_CHILDREN" in select_clause, (
f"{chart_type}: metric missing from outer SELECT:\n{sql}"
)
select_clause = select_clause.replace("SELECT", "").strip()
assert select_clause, f"{chart_type}: empty SELECT list:\n{sql}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ddpui/tests/core/charts/test_query_generation_multiple_dimensions.py` around
lines 555 - 586, Update the pagination chart tests around _compile_sql and the
chart-specific test methods to extract the outer SELECT clause rather than
searching the full SQL string. Assert that this clause includes partner_name and
the expected metric alias for each bar, line, and pie chart, while retaining the
existing grouping and pagination assertions.

@himanshudube97
himanshudube97 self-requested a review August 18, 2026 05:32
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.

3 participants