-
Notifications
You must be signed in to change notification settings - Fork 2.1k
fix(tesseract): reject FILTER_PARAMS string columns under a calendar time shift #11772
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
paveltiunov
wants to merge
16
commits into
master
Choose a base branch
from
claude/filter-params-date-injection-pm2qgo
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+645
−13
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
ebdc764
test(tesseract): pin FILTER_PARAMS behaviour under named calendar tim…
claude ec9e3a7
test(schema-compiler): FILTER_PARAMS push-down under a named calendar…
claude be026dd
fix(tesseract): reject FILTER_PARAMS string columns under a calendar …
claude 6aa097b
fix(tesseract): carry interval-declared calendar shifts, probe the PK…
claude f259ace
refactor(tesseract): trim filter-params shift comments to what the co…
claude f690585
test(tesseract): cover the non-PK calendar binding; docs: FILTER_PARA…
claude 1c57118
refactor(tesseract): fold the calendar arms, correct the PK-probe com…
claude bda197c
docs(tesseract): refresh the stale header on the calendar time-shift …
claude 08f3495
test(tesseract): pin the non-PK probe test to this rejection, not any…
claude 70ba23e
docs(schema-compiler): refresh the stale header on the YAML calendar …
claude ce09d2e
docs: pair the time-shift FILTER_PARAMS example with a JavaScript twin
claude 94d3a60
docs: name the dialect in the time-shift FILTER_PARAMS example
claude c8a9739
docs: cast the filter values in the time-shift FILTER_PARAMS example
claude 1fb4b70
docs: parenthesise the OR band in the time-shift FILTER_PARAMS example
claude 30a9b6a
docs: show the calendar join in the time-shift FILTER_PARAMS example
claude 3dea13a
docs: move the cast and paren notes below the fence
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
118 changes: 118 additions & 0 deletions
118
packages/cubejs-schema-compiler/test/unit/filter-params-calendar-time-shift.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| import { PostgresQuery } from '../../src/adapter/PostgresQuery'; | ||
| import { prepareYamlCompiler } from './PrepareCompiler'; | ||
|
|
||
| // A calendar shift declared with `sql` maps through a column on the calendar | ||
| // (`prior_fiscal_year` -> `next_fiscal_year_d`) rather than offsetting the date, | ||
| // so a string FILTER_PARAMS column is rejected and a callback — handed the | ||
| // query's own bounds — is not. Interval-declared shifts are offset onto the | ||
| // column instead; that shape is pinned in the planner suite. | ||
| const model = (filterParams: string) => ` | ||
| cubes: | ||
| - name: fpc_calendar | ||
| calendar: true | ||
| sql: > | ||
| SELECT '2026-06-20'::date AS calendar_d, | ||
| '2025-06-21'::date AS next_fiscal_year_d, | ||
| '2024-06-22'::date AS next_two_fiscal_year_d | ||
| dimensions: | ||
| - name: calendar_d | ||
| sql: calendar_d | ||
| type: time | ||
| primary_key: true | ||
| time_shift: | ||
| - name: prior_fiscal_year | ||
| sql: "{CUBE}.next_fiscal_year_d" | ||
| - name: prior_two_fiscal_year | ||
| sql: "{CUBE}.next_two_fiscal_year_d" | ||
|
|
||
| - name: fpc_margin | ||
| sql: > | ||
| SELECT * FROM fpc_margin WHERE ${filterParams} | ||
| joins: | ||
| - name: fpc_calendar | ||
| sql: "{CUBE}.week_end_d = {fpc_calendar.calendar_d}" | ||
| relationship: many_to_one | ||
| dimensions: | ||
| - name: id | ||
| sql: id | ||
| type: number | ||
| primary_key: true | ||
| - name: week_end_d | ||
| sql: week_end_d | ||
| type: time | ||
| measures: | ||
| - name: net_sales | ||
| sql: net_sales_a | ||
| type: sum | ||
| - name: net_sales_ly | ||
| multi_stage: true | ||
| sql: "{net_sales}" | ||
| type: number | ||
| time_shift: | ||
| - name: prior_fiscal_year | ||
| - name: net_sales_ly2 | ||
| multi_stage: true | ||
| sql: "{net_sales}" | ||
| type: number | ||
| time_shift: | ||
| - name: prior_two_fiscal_year | ||
| `; | ||
|
|
||
| const STRING_COLUMN = '{FILTER_PARAMS.fpc_calendar.calendar_d.filter(\'week_end_d\')}'; | ||
|
|
||
| // The band a model writes by hand once it knows the shifted periods it has to | ||
| // cover. 371/364 days back brackets the fiscal prior year. YAML `.filter()` | ||
| // bodies are Python, so this is a lambda — the same form the reporting models | ||
| // that hit this use. | ||
| const CALLBACK_COLUMN = '{FILTER_PARAMS.fpc_calendar.calendar_d.filter(' | ||
| + 'lambda x, y: f"(week_end_d >= {x} AND week_end_d <= {y}) ' | ||
| + 'OR (week_end_d >= {x}::timestamptz - interval \'371 day\' ' | ||
| + 'AND week_end_d <= {y}::timestamptz - interval \'364 day\')")}'; | ||
|
|
||
| async function buildSql(filterParams: string): Promise<[string, unknown[]]> { | ||
| const { compiler, joinGraph, cubeEvaluator } = prepareYamlCompiler(model(filterParams)); | ||
| await compiler.compile(); | ||
|
|
||
| return new PostgresQuery({ joinGraph, cubeEvaluator, compiler }, { | ||
| measures: ['fpc_margin.net_sales_ly', 'fpc_margin.net_sales_ly2'], | ||
| timeDimensions: [{ | ||
| dimension: 'fpc_calendar.calendar_d', | ||
| dateRange: ['2026-06-20', '2026-06-20'], | ||
| }], | ||
| timezone: 'UTC', | ||
| // Named calendar time shifts are planned by Tesseract only. | ||
| useNativeSqlPlanner: true, | ||
| }).buildSqlAndParams(); | ||
| } | ||
|
|
||
| describe('FILTER_PARAMS under a named calendar time shift', () => { | ||
| // Before this was rejected the column rendered bare and was bound to the | ||
| // unshifted reporting bounds in every stage. Each stage joins the calendar on | ||
| // its own mapping column, so the pushed-down predicate contradicted the stage | ||
| // around it and the stage came back empty — the same failure CORE-543 fixed | ||
| // for interval shifts, reached by a different route. | ||
| it('rejects a string column', async () => { | ||
| await expect(buildSql(STRING_COLUMN)).rejects.toThrow( | ||
| /fpc_calendar\.calendar_d.*prior_fiscal_year.*callback/s | ||
| ); | ||
| }); | ||
|
|
||
| // A callback column is handed the query's bounds and decides the range | ||
| // itself, so it is left alone. This is what a model widened by hand relies | ||
| // on, and it must keep working. | ||
| it('pushes a callback column into every shifted stage', async () => { | ||
| const [sql] = await buildSql(CALLBACK_COLUMN); | ||
|
|
||
| expect(sql.match(/week_end_d >=/g)).toHaveLength(4); | ||
| expect(sql).not.toContain('FROM fpc_margin WHERE (1 = 1)'); | ||
| }); | ||
|
|
||
| // The stage predicate is what carries the shift: each stage compares the | ||
| // calendar's own mapping column, not `calendar_d`. | ||
| it('filters each stage on its own mapping column', async () => { | ||
| const [sql] = await buildSql(CALLBACK_COLUMN); | ||
|
|
||
| expect(sql).toContain('next_fiscal_year_d >='); | ||
| expect(sql).toContain('next_two_fiscal_year_d >='); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.