Skip to content

fix(dataset): correct Hours Offset filter bounds and grain-truncation order - #43173

Draft
sadpandajoe wants to merge 3 commits into
masterfrom
fix-dataset-hours-offset-bound-truncation
Draft

fix(dataset): correct Hours Offset filter bounds and grain-truncation order#43173
sadpandajoe wants to merge 3 commits into
masterfrom
fix-dataset-hours-offset-bound-truncation

Conversation

@sadpandajoe

Copy link
Copy Markdown
Member

SUMMARY

The dataset "Hours offset" setting had two independent bugs:

  1. Filter bounds could admit or drop a day. For a DATE-typed column, the
    filter bound was shifted by the raw Hours Offset before being rendered as a
    date-only literal. Any sub-24h offset could move which calendar day a bound
    truncated to -- silently admitting an out-of-range day on one end while
    dropping the last requested day on the other. TIMESTAMP-typed columns were
    unaffected (they keep exact-hour precision).

  2. Displayed labels could disagree with the filter bounds. The offset was
    applied only in pandas, after the database had already truncated a temporal
    axis to its time grain using the raw (unshifted) value. A row near a grain
    boundary could bucket to the wrong day, so the label shown to the user could
    disagree with which rows the filter actually selected.

FIX

  1. The offset is now quantized to whole days (truncating toward zero) before
    being applied to a DATE column's filter bounds, so a sub-day offset never
    moves the selected day window. This part is engine-agnostic.

  2. The offset is now applied in SQL, before grain truncation, at the
    axis-construction call sites -- gated behind a new apply_dataset_offset
    opt-in parameter so filter construction and other callers are unaffected.
    A new sql_shifted_temporal_labels signal tells dataframe normalization
    which columns were already shifted in SQL, so the offset isn't re-applied
    on top in pandas.

    Since the SQL-side shift depends on dialect-specific interval syntax, it's
    gated behind a new BaseEngineSpec.supports_temporal_column_shift
    capability flag (default False), enabled explicitly on the two engines
    it's verified against (PostgreSQL, SQLite). Engines that don't opt in keep
    the prior pandas-only behavior for bug (2) -- no new regression -- while
    unconditionally gaining the bound-quantization fix for bug (1), which is
    engine-agnostic.

TESTING

Added tests/unit_tests/models/test_hours_offset_bound_truncation.py, covering
both bugs across an offset x grain x column-type matrix: DATE-column filter
bounds across offsets 0/+1/+24/+25/-1/-25 (with a TIMESTAMP control), grained
physical and adhoc BASE_AXIS temporal axes applying the offset exactly once
before truncation, negative sub-day DATE offsets that must not move the
bucket, byte-identical SQL for the explicitly out-of-scope grained-filter
paths, and the new per-engine capability gate (including its pandas-fallback
path for unsupported engines).

ADDITIONAL SCOPE

Grained temporal filters combined with a shifted grained axis are out of
scope for this change (the filter path is unchanged) and may need a follow-up.
DST handling and the extra.timezone override path are also unchanged.

… order

The dataset "Hours offset" setting had two independent bugs:

1. Filter bounds shifted a DATE-typed column's literal bound by the raw
   offset before rendering it as a date-only value. A sub-24h offset could
   move which calendar day a bound truncated to, silently admitting an
   out-of-range day on one end and dropping the last requested day on the
   other. The offset is now quantized to whole days (truncating toward
   zero) before it's applied to a DATE column's bounds, so a sub-day offset
   never moves the selected day window.

2. The offset was applied only in pandas, after the database had already
   truncated a temporal axis to its time grain using the raw (unshifted)
   value. A row near a grain boundary could bucket to the wrong day, and
   the displayed label (computed via a different path) could disagree with
   the filter bounds. The offset is now applied in SQL, before grain
   truncation, at the axis-construction call sites -- gated behind a new
   `apply_dataset_offset` opt-in so filter construction and other unrelated
   callers are unaffected. A new `sql_shifted_temporal_labels` signal tells
   dataframe normalization which columns were already shifted in SQL so the
   legacy pandas-side offset isn't re-applied on top.

Since the SQL-side shift depends on dialect-specific interval syntax, it's
gated behind a new `BaseEngineSpec.supports_temporal_column_shift` capability
flag (default False), enabled explicitly on the two engines it's been
verified against (Postgres, SQLite). Engines that don't opt in keep the
prior pandas-only behavior for the grain-truncation-order bug -- no new
regression -- while gaining the DATE-bound quantization fix unconditionally
(that part is engine-agnostic).
Covers both defects across an offset x grain x column-type matrix:

- DATE-column filter bounds stay on the requested whole-day window for
  offsets 0/+1/+24/+25/-1/-25 (the bug admitted/dropped a day for any
  sub-24h offset); TIMESTAMP columns keep exact-hour bounds as a control.
- Grained physical and adhoc BASE_AXIS temporal axes apply the offset
  exactly once, before grain truncation, matching the exact SQL literal
  and the resulting bucket for each combination -- including the case
  that previously applied the offset zero times for an ungrained axis,
  and negative sub-day DATE offsets that must not move the bucket.
  Physical and adhoc filter SQL are asserted byte-identical to pre-fix
  output for the explicitly out-of-scope grained-filter paths.
- The new per-engine capability gate correctly excludes every engine
  that doesn't support the SQL-side shift (falls back to the pre-fix
  pandas-only offset, verified against the real code path with no
  shift SQL emitted) and correctly includes Postgres/SQLite (compiled
  SQL asserted exactly, across signed and >=24h offsets).
@netlify

netlify Bot commented Aug 14, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 547401b
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a7f535d95817300086db160
😎 Deploy Preview https://deploy-preview-43173--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov

codecov Bot commented Aug 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 65.45455% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.68%. Comparing base (537f0fd) to head (7357d03).
⚠️ Report is 20 commits behind head on master.

Files with missing lines Patch % Lines
superset/connectors/sqla/models.py 47.36% 7 Missing and 3 partials ⚠️
superset/models/helpers.py 62.50% 6 Missing and 3 partials ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##           master   #43173   +/-   ##
=======================================
  Coverage   66.67%   66.68%           
=======================================
  Files        2871     2871           
  Lines      163328   163362   +34     
  Branches    37674    37681    +7     
=======================================
+ Hits       108904   108933   +29     
+ Misses      52296    52295    -1     
- Partials     2128     2134    +6     
Flag Coverage Δ
hive 38.21% <21.81%> (-0.01%) ⬇️
mysql 57.92% <49.09%> (+<0.01%) ⬆️
postgres 57.96% <61.81%> (+<0.01%) ⬆️
presto 40.16% <30.90%> (+<0.01%) ⬆️
python 59.35% <65.45%> (+0.01%) ⬆️
sqlite 57.60% <56.36%> (+0.01%) ⬆️
unit 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ 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.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…t param

normalize_df now accepts an optional third sql_shifted_temporal_labels
parameter (passed positionally from processing_time_offsets). Three
pre-existing mocks in this file stubbed normalize_df with a fixed two-argument
lambda, which broke when the call site started passing a third positional
argument.
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.

1 participant