Skip to content

fix(mcp): validate temporal SIMPLE filter values before they reach SQL - #46110

Draft
aminghadersohi wants to merge 3 commits into
masterfrom
sc-128754-temporal-filter-value-validation
Draft

aminghadersohi wants to merge 3 commits into
masterfrom
sc-128754-temporal-filter-value-validation

Conversation

@aminghadersohi

@aminghadersohi aminghadersohi commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

SUMMARY

A SIMPLE filter on a temporal column accepted any value. Through the MCP chart tools and query_dataset:

  • {"column": "ds", "op": ">=", "value": "2000"} was read as epoch milliseconds and became ds >= '1970-01-01 00:00:02', so "since 2000" silently returned nearly everything.
  • "not-a-date" reached SQL as a string literal (an empty result on SQLite, a database error on engines with typed timestamps).

Reproduced with the real SqlaTable.get_sqla_query on a SQLite DATETIME column: "2000" renders WHERE ds >= '1970-01-01 00:00:02', "not-a-date" renders WHERE ds >= 'not-a-date'.

The conversion lives in ExploreMixin.filter_values_handler, which reads an all-digit string on a temporal column as epoch milliseconds and passes every other string through. That is deliberate (drill-to-detail and cross-filters depend on it, see #40180 and the test_temporal_*_filter_* tests in tests/unit_tests/models/helpers_test.py), so the shared query path is left unchanged. The check is added in the MCP layer instead, where the dataset's column types are known:

  • New invalid_temporal_filter_value helper (superset/mcp_service/common/temporal_filter_values.py). For =, !=, <, <=, >, >=, IN, NOT IN on a temporal column, strings must be ISO 8601 and numbers are epoch milliseconds. An all-digit string is rejected as ambiguous between a year and an epoch. Booleans and unparseable strings are rejected. LIKE-style operators and IS NULL / IS NOT NULL are not checked.
  • DatasetValidator._validate_filter_references applies it to filters and filters_secondary of typed chart configs. Typed configs in generate_chart, update_chart, update_chart_preview, and generate_explore_link funnel through validate_against_dataset and return an INVALID_TEMPORAL_FILTER_VALUE validation error before querying invalid temporal comparators. get_chart_preview reads saved/native chart state rather than a typed config and is unchanged.
  • query_dataset applies the same check to its filters.

Not changed: non-temporal columns, valid ISO dates and datetimes, numeric epoch milliseconds, and null checks all behave as before. Direct chart data API requests (/api/v1/chart/data) are not validated here, because there epoch-millisecond strings are an intended input.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A, backend only.

TESTING INSTRUCTIONS

pytest tests/unit_tests/mcp_service/common/test_temporal_filter_values.py \
  tests/unit_tests/mcp_service/chart/validation/test_dataset_validator.py \
  tests/unit_tests/mcp_service/chart/test_compile.py \
  tests/unit_tests/mcp_service/dataset/tool/test_query_dataset.py

The rejection regressions fail with the two modified call sites restored to master (9 original cases and 6 mixed-timeseries cases, including filters_secondary, filters_b, and case-folded column names). The four test files above pass locally: 334 passed, 1 skipped; acceptance cases also pin unchanged valid-input behavior. Manually: call generate_chart or query_dataset with a filter {"column": "<temporal col>", "op": ">=", "value": "2000"} and observe a validation error suggesting 2000-01-01; "2000-01-01" and 946684800000 still run.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in [SIP-59] Proposal for Database migration standards #13351)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

@codecov

codecov Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 25.00000% with 27 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.35%. Comparing base (a131fcb) to head (ac141b1).

Files with missing lines Patch % Lines
...erset/mcp_service/common/temporal_filter_values.py 23.07% 20 Missing ⚠️
superset/mcp_service/dataset/tool/query_dataset.py 28.57% 5 Missing ⚠️
.../mcp_service/chart/validation/dataset_validator.py 33.33% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #46110      +/-   ##
==========================================
- Coverage   65.35%   65.35%   -0.01%     
==========================================
  Files        3083     3084       +1     
  Lines      211224   211260      +36     
  Branches    49803    49813      +10     
==========================================
+ Hits       138054   138063       +9     
- Misses      69992    70019      +27     
  Partials     3178     3178              
Flag Coverage Δ
hive 34.05% <25.00%> (-0.01%) ⬇️
mysql 51.80% <25.00%> (-0.01%) ⬇️
postgres 51.83% <25.00%> (-0.01%) ⬇️
presto 35.69% <25.00%> (-0.01%) ⬇️
python 52.12% <25.00%> (-0.01%) ⬇️
sqlite 51.51% <25.00%> (-0.01%) ⬇️

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.

This branch has not been deployed

No deployments
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