Skip to content

fix(query_object_factory): backport deprecated-field normalization to 6.0-bug-fixes (#41204)#41909

Open
eschutho wants to merge 1 commit into
apache:6.0-bug-fixesfrom
eschutho:fix/backport-query-object-factory-deprecated-fields
Open

fix(query_object_factory): backport deprecated-field normalization to 6.0-bug-fixes (#41204)#41909
eschutho wants to merge 1 commit into
apache:6.0-bug-fixesfrom
eschutho:fix/backport-query-object-factory-deprecated-fields

Conversation

@eschutho

@eschutho eschutho commented Jul 9, 2026

Copy link
Copy Markdown
Member

What changed

Deprecation warning found in production Datadog logs (still firing, ~250/day):

The field `groupby` is deprecated, please use `columns` instead.
  file: query_object.py, line 225, func: _rename_deprecated_fields

along with the sibling timeseries_limit, timeseries_limit_metric, and granularity_sqla warnings from the same code path.

This exact warning was already fixed on master via #41204 (commit 7040388), merged 2026-06-18. That fix normalizes the four DEPRECATED_FIELDS inside QueryObjectFactory.create() before constructing QueryObject, so QueryObject._rename_deprecated_fields never sees the old keys and never logs.

The fix was never backported to 6.0-bug-fixes. Comparing release tags: v6.1.0.2 (2026-06-29) contains commit 7040388; v6.0.0.19 (2026-07-02) does not. That's why this warning is still noisy in production three weeks after the master fix landed — production is on the 6.0.x line, which never got the backport.

What was changed

Clean git cherry-pick -x 7040388ad15605bc22f32665fe4edc157fa53564 onto 6.0-bug-fixes. No manual edits, no conflicts. Touches the same 2 files as the original PR:

  • superset/common/query_object_factory.py (+6/-1)
  • tests/unit_tests/common/test_query_object_factory.py (+87, new tests)

No behavior change

Charts using stored query_context with legacy groupby/timeseries_limit keys continue to produce the same result — only the noisy warning log is suppressed. See original PR #41204 for full rationale, including the bonus correctness fix to x-axis temporal filter resolution for stored groupby-keyed contexts.

Test plan

  • Cherry-pick applies cleanly, diff verified identical to master commit modulo cherry-pick metadata.
  • Self-reviewed: confirmed all production callers of QueryObject route through QueryObjectFactory.create(), so the fix has full coverage on this branch; confirmed falsy-value (e.g. timeseries_limit=0) semantics are unchanged from pre-fix behavior.
  • CI green on this branch.

@bito-code-review

bito-code-review Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Bito Automatic Review Skipped - Branch Excluded

Bito didn't auto-review because the source or target branch is excluded from automatic reviews.
No action is needed if you didn't intend for the agent to review it. Otherwise, to manually trigger a review, type /review in a comment and save.
You can change the branch exclusion settings here, or contact your Bito workspace admin at evan@preset.io.

Comment on lines +71 to +72
for field in DEPRECATED_FIELDS:
if old_val := kwargs.pop(field.old_name, None):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion: Add explicit type annotations for the newly introduced variables in the deprecated-field normalization block so the new logic is fully typed. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

The added old_val local variable is introduced in new Python code and has no type annotation. This matches the rule requiring new or changed Python variables to include type hints.

Rule source 📖

.cursor/rules/dev-standard.mdc (line 28)

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset/common/query_object_factory.py
**Line:** 71:72
**Comment:**
	*Custom Rule: Add explicit type annotations for the newly introduced variables in the deprecated-field normalization block so the new logic is fully typed.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +142 to +146
def test_deprecated_groupby_renamed_to_columns(
self,
query_object_factory: QueryObjectFactory,
raw_query_context: dict[str, Any],
):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion: Add an explicit return type annotation to this new test method signature (for example, annotate it as returning None). [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

This is a newly added Python test method and it lacks an explicit return type annotation. The custom rule requires new or changed Python functions and methods to include proper type hints, so -> None should be added.

Rule source 📖

.cursor/rules/dev-standard.mdc (line 28)

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** tests/unit_tests/common/test_query_object_factory.py
**Line:** 142:146
**Comment:**
	*Custom Rule: Add an explicit return type annotation to this new test method signature (for example, annotate it as returning `None`).

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@eschutho eschutho requested a review from richardfogaca July 9, 2026 16:27
Comment on lines +71 to +73
for field in DEPRECATED_FIELDS:
if old_val := kwargs.pop(field.old_name, None):
kwargs.setdefault(field.new_name, old_val)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion: The deprecated-field normalization uses setdefault, which treats a key with value None as already populated. If a payload includes both a deprecated key (for example groupby) and a canonical key set to None (for example columns=None), the deprecated value is dropped after pop and never propagated, causing dimensions/limits to be lost. Update the merge logic to treat None (and any other "unset" sentinel you consider empty) as missing so valid deprecated values are preserved. [incorrect condition logic]

Severity Level: Major ⚠️
- ❌ /v1/query payloads can lose groupby dimensions.
- ❌ Affected charts render ungrouped or with incorrect aggregates.
- ⚠️ Stored query_contexts mixing keys may silently drop fields.
Steps of Reproduction ✅
1. Craft a `query_context` JSON payload for the `/v1/query/` endpoint
(superset/views/api.py:60-17) where `queries[0]` includes both `"columns": null` and
`"groupby": ["name", "gender"]`, along with normal fields like `metrics`, `granularity`,
and `time_range`.

2. Submit this payload to `POST /v1/query/`; in `Api.query()` (superset/views/api.py:4-17)
the request body is deserialized and passed into
`QueryContextFactory.create(**json.loads(request.form["query_context"]))`.

3. Inside `QueryContextFactory.create()` (superset/common/query_context_factory.py:7-48),
each raw query dict is handed to `self._query_object_factory.create(...)` with
`**query_obj`, so `kwargs` in `QueryObjectFactory.create()`
(superset/common/query_object_factory.py:52-62) contains both `columns=None` and
`groupby=["name", "gender"]`.

4. In `QueryObjectFactory.create()` the deprecated-field normalization loop
(superset/common/query_object_factory.py:69-73) pops `groupby` into `old_val` and then
calls `kwargs.setdefault("columns", old_val)`. Because `columns` already exists with value
`None`, `setdefault` treats the key as populated and does not overwrite it, so `columns`
remains `None` and `groupby` is removed. When the `QueryObject` is constructed, `columns`
is interpreted as empty (`self.columns = columns or []` in
superset/common/query_object.py:142), causing the intended groupby dimensions to be lost
in downstream processing.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset/common/query_object_factory.py
**Line:** 71:73
**Comment:**
	*Incorrect Condition Logic: The deprecated-field normalization uses `setdefault`, which treats a key with value `None` as already populated. If a payload includes both a deprecated key (for example `groupby`) and a canonical key set to `None` (for example `columns=None`), the deprecated value is dropped after `pop` and never propagated, causing dimensions/limits to be lost. Update the merge logic to treat `None` (and any other "unset" sentinel you consider empty) as missing so valid deprecated values are preserved.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@richardfogaca

Copy link
Copy Markdown
Contributor

Richard's agent here: I think this backport changes legacy field precedence in a way that is causing the relevant integration failures.

What changed

QueryObject._rename_deprecated_fields() used to let a truthy deprecated field replace the canonical attribute when both were present. The new factory-level normalization uses setdefault, so the already-present canonical value wins instead:

kwargs.setdefault(field.new_name, old_val)

Why this matters

The failing integration tests match that behavior change:

  • test_convert_deprecated_fields expects granularity_sqla="timecol" to override an existing granularity="ds", but it now keeps "ds".
  • test_time_offsets_accuracy / test_time_offsets_in_query_object_no_limit expect legacy groupby to override existing columns; with setdefault, the grouping is dropped, leading to the missing state/name columns.

Suggested fix

Could we preserve the old truthy-value override semantics while still popping deprecated keys before constructing QueryObject? For example, when old_val is truthy, assign kwargs[field.new_name] = old_val instead of using setdefault.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants