Skip to content

docs: rewrite the custom calendar recipe onto calendar cubes - #11750

Open
igorlukanin wants to merge 6 commits into
masterfrom
igor/core-855-docs-add-a-recipe-for-4-5-4-calendars-with-calendar-cubes
Open

docs: rewrite the custom calendar recipe onto calendar cubes#11750
igorlukanin wants to merge 6 commits into
masterfrom
igor/core-855-docs-add-a-recipe-for-4-5-4-calendars-with-calendar-cubes

Conversation

@igorlukanin

Copy link
Copy Markdown
Member

The custom calendar recipe was written before calendar cubes existed: it modelled a 4-5-4 retail calendar with a join plus proxy dimensions, and its retail_week was a type: number dimension rather than a granularity. That is the workaround, not the feature. This rewrites the page onto calendar cubes and keeps the URL.

The model is derived from the fixture the Rust planner tests cover, so every snippet reflects tested behaviour: a calendar: true cube whose primary_key dimension overrides the month, week, and year granularities with pre-calculated columns, plus prior-month and prior-year time shifts. The page also states why a 4-5-4 month needs sql: rather than interval and links to the naming rule instead of restating it.

Both the YAML and the JavaScript version of the full model were compiled against the schema compiler before publishing. That turned up three things worth calling out:

  • The JavaScript granularities form was wrong in the existing docs. It must be an object keyed by granularity name; the array form does not compile. The calendar cubes page had it as an array in two places, so those are fixed here too.
  • sql: '{CUBE}.col' in JavaScript is a plain string. Only a template literal is transpiled to receive CUBE, so the single-quoted form on the calendar cubes page silently produced a literal instead of a column reference. Also fixed.
  • extends does not carry calendar: true to the query engine. The child reads it through the prototype chain, but it is not an own property, so it is not serialised and the extending cube is not treated as a calendar cube — its granularity overrides are quietly ignored. The recipe now shows calendar: true repeated on the extending cube, with a warning explaining why.

The measure snippets are now shown on the cube with facts. Previously they were bare fragments, and a reader who put them on the calendar cube would have counted dates instead of orders.

Also updated the recipe index card, which still described the proxy-dimension approach.

Verified with mint broken-links --check-anchors (clean; the one reported break is pre-existing and unrelated) and by rendering both changed pages locally.

@mintlify

mintlify Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
cubed3 🟢 Ready View Preview Sep 3, 2026, 11:10 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

@claude

claude Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Claude finished @igorlukanin's task in 2m 39s —— View job


Approve with 3 non-blocking comments — 0 high, 2 medium, 1 low

Full review
  • Read the diff
  • Verify claims against the codebase
  • Check links, anchors, and inbound references
  • Post review

Verification

I checked the three claims in the PR description against the source rather than taking them at face value:

Claim Verdict
JS granularities must be an object keyed by name Confirmed — CubeValidator.ts:1450 iterates Object.entries(dimension.granularities), and the sql-must-be-named-after-a-default rule is at CubeValidator.ts:1455
extends does not carry calendar: true through Confirmed at the mechanism level — CubeSymbols.ts:541 uses Object.setPrototypeOf(cubeObject, parentCube), so calendar is never an own property of the child. The bridge deserialises it as is_calendar (cube_definition.rs:21-22)
sql: '{CUBE}.col' in JS is a plain string Correct. Swept the whole docs tree: no remaining '{CUBE} in a JS block, and no remaining granularities: [ anywhere

Also verified: the #naming-a-granularity-defined-with-sql anchor exists (calendar-cubes.mdx:471); the v1.7.0 phrasing matches the 15 other pages that use it; all four inbound links to this recipe (recipes/index.mdx, custom-granularity.mdx:29, dimensions.mdx:152, calendar-cubes.mdx:502) still describe the page accurately after the rewrite; docs.json:661 unchanged, URL preserved.

What's good

The rewrite is a clear improvement. Deriving the model from the fixture the Rust planner tests cover is the right call, and it shows — the date_prev_month / date_prev_year columns and the 4 – 5 – 4 walk through 2024-02-04 → 2024-03-03 → 2024-04-07 are internally consistent, including the five-week middle month. Moving the measure snippets onto the cube with facts fixes a genuine trap. The <Info> about overriding on the joined dimension and the <Warning> about repeating calendar: true both earn their place: each documents a silent-wrong-answer failure, not a crash.

Findings

# Severity File Issue
1 Medium recipes/data-modeling/custom-calendar.mdx:83-92 quarter is described in prose but never overridden — grouping by it silently returns Gregorian quarters
2 Medium docs/data-modeling/concepts/calendar-cubes.mdx:103-107 Pre-existing: overrides sit on date while primary_key and the join are on date_key — the exact case the new callout warns about
3 Low recipes/data-modeling/custom-calendar.mdx:220-236 retail_calendar_completed is defined but the accompanying join on orders is never shown

Details are in the three inline comments. None block merge; #2 is pre-existing but the PR is already editing those lines and adding the callout that makes it read as wrong.

Minor, not worth an inline comment

The Pre-aggregations section is prose-only. Given the section's point is "declare the overridden granularity explicitly", a four-line rollup with granularity: month on it would make that concrete faster than the two paragraphs do. Your call — the page is otherwise commendably tight against the docs style guide's "be concise" rule.

· [`igor/core-855-docs-add-a-recipe-for-4-5-4-calendars-with-calendar-cubes`](https://github.com/cube-js/cube/tree/igor/core-855-docs-add-a-recipe-for-4-5-4-calendars-with-calendar-cubes)

Comment thread docs-mintlify/recipes/data-modeling/custom-calendar.mdx
Comment thread docs-mintlify/docs/data-modeling/concepts/calendar-cubes.mdx
Comment thread docs-mintlify/recipes/data-modeling/custom-calendar.mdx
@claude

claude Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Claude finished @igorlukanin's task in 1m 37s —— View job


Approve — incremental push is clean; 3 prior threads still open (2 medium, 1 low)

Full review of the new commit
  • Diff the latest push against the previous review
  • Check whether the 3 open threads still apply
  • Verify new/changed claims against the codebase
  • Post updated review

What changed since the last review

Only 8c6ac92 is new. Two things:

1. docs.json:160calendar-cubes added to the Data Modeling sidebar.

Verified: the file parses, the entry appears exactly once (no duplicate under another group), docs/data-modeling/concepts/calendar-cubes.mdx exists, and the placement — after dimensions, before ai-context — sits next to concepts/syntax, which uses the same concepts/ path style. Good catch: the page was reachable by link but had no nav entry, which docs-mintlify/CLAUDE.md calls out as required.

2. custom-calendar.mdx:238-243 — the extends warning softened.

-an inherited value is not passed to the query engine. Without it, the extending cube is
-not treated as a calendar cube and its granularity overrides are ignored.
+inherited cube-level parameters are not always passed to the query engine. Setting it
+again keeps the extending cube a calendar cube.

Still accurate. The mechanism is unchanged — CubeSymbols.ts:541 does Object.setPrototypeOf(cubeObject, parentCube), so calendar is never an own property of the child and does not survive serialisation to the native planner, which reads it as is_calendar. calendar appears in the schema compiler only as a type declaration (CubeSymbols.ts:229) and a validator entry; nothing in JS consumes it, which is consistent with the flag mattering only on the Tesseract side.

The hedge does cost the reader the why: "not always" leaves them unable to predict which other cube-level parameters behave this way, where the previous sentence told them exactly what breaks. Both versions get them to the right code, so this is a judgement call and yours to make — not raising it as a finding.

Prior threads

All three are still unresolved and still apply — the new commit touches none of the relevant lines. Re-affirmed rather than re-posted, so no duplicate inline comments:

Severity File Issue
Medium recipes/data-modeling/custom-calendar.mdx:92 quarter described in prose but never overridden
Medium docs/data-modeling/concepts/calendar-cubes.mdx:107 Overrides on date, primary_key and join on date_key
Low recipes/data-modeling/custom-calendar.mdx:236 retail_calendar_completed defined but its join never shown

None block merge. The second is the one I'd most want closed in this PR, since the page now carries a callout that makes its own example read as wrong.

· [`igor/core-855-docs-add-a-recipe-for-4-5-4-calendars-with-calendar-cubes`](https://github.com/cube-js/cube/tree/igor/core-855-docs-add-a-recipe-for-4-5-4-calendars-with-calendar-cubes)

@claude

claude Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Claude finished @igorlukanin's task in 2m 39s —— View job


Approve — all 3 prior threads addressed and resolved; 1 new low note

Full review of `29f9ee4`
  • Diff 29f9ee4 against the previous review
  • Check whether the 3 open threads are addressed
  • Verify new claims against the codebase
  • Resolve stale threads, post updated review

Prior threads — all closed

Prior finding Status
Medium — quarter described but never overridden Fixed. retail_quarter_begins added to the table, a fourth override in both snippets, frontmatter + intro bullet + closing sentence all updated, and the 2024-05-05 row makes the quarter boundary visible. quarter is in TIME_SERIES (CubeValidator.ts:113), so the override is valid
Medium — overrides on date, primary_key on date_key Fixed. date_key dropped from all three examples on calendar-cubes.mdx; primary_key: true moved onto date alongside the overrides, and the two join snippets updated to {fiscal_calendar.date} / {custom_calendar.date}. No date_key remains in either changed file (the hits in reference/data-modeling/{dimensions,measures}.mdx are separate pages, out of scope)
Low — retail_calendar_completed never joined Fixed. The second join is now shown in both YAML and JS, with completed_at alongside it

All three resolved.

New claims, verified

multi_stage: true on the time-shift measures — correct, and a real compile fix. timeShift on a measure only exists inside the multi-stage branch of the measure schema (CubeValidator.ts:960), so the previous snippets would have been rejected. Same for sql: '{total_sales}'`${total_sales}` in JS.

The new <Warning> on multiple primary keys — accurate, including the quoted message. dimension_symbol.rs:433-438 raises exactly Cube '{}' has multiple primary keys, but only one is allowed for calendar cubes, and it fires at symbol-resolution time, not compile time, which is what "compiles without error but makes every query… fail" says.

The reworded extends warning is the most interesting change, and it is right. The claim flipped from "granularity overrides are ignored" to "overrides still apply, time shifts revert to interval arithmetic". That matches the source:

  • is_calendar is read in exactly one place (dimension_symbol.rs:417) and gates only time_shift_pk / is_self_time_shift_pk. Both calendar_time_shift_for_interval and calendar_time_shift_for_named_interval (lines 257, 279) return None without one of them — so the custom sql shift is dropped and the planner falls back to INTERVAL '1 month'.
  • Granularity overrides go through the general custom-granularity path with no calendar check; CubeValidator.ts:1044 even carries a // TODO should be valid only for calendar cubes confirming the flag is not consulted.

So the earlier, stronger version of this warning was wrong in the specific it named, and the new one is both more precise and more useful — it names the symptom a reader would actually see.

The <Info> gaining "the same is true per granularity: this cube still answers day with DATE_TRUNC" is a good addition, though day is a slightly odd illustration: in a 4-5-4 calendar the retail day is the Gregorian day, so nothing is lost there. The point lands anyway.

New finding

# Severity File Issue
1 Low recipes/data-modeling/custom-calendar.mdx:419-427 Pre-aggregations section is the only part of the page not backed by a planner test

Every other snippet on the page traces to a fixture the Rust planner covers — that is the PR's stated method and it holds up. The Pre-aggregations section is the exception: there is no test under rust/cube/cubesqlplanner/.../tests/integration/pre_aggregations/ that mentions a calendar cube, so "a rollup on month is built from the retail_month_begins column and serves queries at month" rests on the general custom-granularity rollup rule rather than on observed behaviour for calendar cubes. Worth confirming by hand before publishing, or softening to point at the custom-granularity pre-aggregation docs. Not a blocker — the guidance is conservative in the right direction (declare it explicitly, don't rely on a finer rollup).

Minor, not worth an inline comment

The second orders snippet (line 250) repeats cubes: - name: orders / sql_table: orders in full, while the later measure snippets use - name: orders + # .... A reader copying both orders blocks defines the cube twice. Trimming the second one to the # ... form used further down would make the partial-snippet convention consistent across the page.

· [`igor/core-855-docs-add-a-recipe-for-4-5-4-calendars-with-calendar-cubes`](https://github.com/cube-js/cube/tree/igor/core-855-docs-add-a-recipe-for-4-5-4-calendars-with-calendar-cubes)

@igorlukanin

Copy link
Copy Markdown
Member Author

Actioned the three review threads, and the middle one grew: the calendar cubes concept page had two examples that could not run at all. The granularity example declares two primary keys, which compiles silently and then fails every query against the cube; the time-shifts example was missing multi_stage: true on its measures, so it failed to compile. Both are fixed here rather than split out, since this PR was already rewriting the callout that made the first one read as wrong.

I compiled every model snippet on both pages and generated SQL for each — all 10 blocks pass now, and the recipe reproduces its own documented 3 / 5 / 4 result table.

One thing I left alone: in the time-shifts example the shifted CTE groups by the shifted column, so total_sales_prior_month returns the same month rather than the previous one. That looks like a product question, not a docs one, and the page only claims which SQL is generated — which is correct. Happy to file it if you agree.

@claude

claude Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Claude finished @igorlukanin's task in 3m 24s —— View job


Request changes on the new commit — 1 medium (the pre-aggregation warning may be inverted); prior 3 threads all resolved

Full review of `0ef1eee`
  • Diff 0ef1eee against the previous review
  • Verify the pre-aggregation claim against the source
  • Check prior threads
  • Post updated review

What changed

Only 0ef1eee is new, and it makes the same edit on both pages: the Pre-aggregations guidance flips from an <Info> saying declare the overridden granularity explicitly to a <Warning> saying do not pre-aggregate these queries at all, on the grounds that the rollup is built with DATE_TRUNC and holds Gregorian buckets.

This is a direct response to the low-severity note I left last round — that the Pre-aggregations section was the one part of the page not backed by a planner test. Turning an unverified positive claim into an unverified negative one doesn't settle it, though, and the negative version is the more costly one to get wrong: it tells readers to give up pre-aggregations for their main reporting grain.

Why I think it may be inverted

Traced both halves of the path; the override survives both as far as I can tell:

Step What the code does
Rollup build preAggregationQueryForSqlEvaluation (BaseQuery.js:4835) builds a source-DB-class query with preAggregationQuery: true; canUseNativeSqlPlannerPreAggregation is true there (BaseQuery.js:365 — only CubeStoreQuery opts out, CubeStoreQuery.ts:85), so Tesseract plans it, and Tesseract reads the override (granularity_helper.rs:229, granularity.rs:107, time_dimension_symbol.rs:265)
Rollup serve Legacy planner, where a sql override named after a predefined granularity is invisible (Granularity.ts:33-56) — but BaseTimeDimension.dimensionSql returns the plain rollup column when context.rollupGranularity === granularityObj.granularity (BaseTimeDimension.ts:114-117), so no re-truncation at the declared grain

The case that does break silently is the one the removed paragraph covered: a query matched to a rollup at a different grain via granularityHierarchies / expandGranularity (PreAggregations.ts:738-742), where the legacy DATE_TRUNC runs over already-bucketed retail dates. So the earlier wording looks closer to correct than the new one, and the new one drops that specific warning.

I could not run the compiler to settle this — this checkout has no node_modules and building the monorepo isn't practical inside the review job — so I may be missing a step. Details and the concrete ask (the generated rollup SQL for a rollup with granularity: month) are in the inline comment on custom-calendar.mdx:423-426. A unit test asserting on that SQL would make this section as test-backed as the rest of the page, which is the standard the PR sets for itself.

Prior threads

All three from earlier rounds are now resolved by the author, and the fixes hold up in the current tree. Nothing re-raised.

Minor

"Query the calendar cube directly for these groupings" (custom-calendar.mdx:432) reads oddly — the reader isn't switching what they query, they're just not adding a rollup. "Leave these queries to run against the source database" would say it plainly. Only worth touching if the warning survives as-is.

· [`igor/core-855-docs-add-a-recipe-for-4-5-4-calendars-with-calendar-cubes`](https://github.com/cube-js/cube/tree/igor/core-855-docs-add-a-recipe-for-4-5-4-calendars-with-calendar-cubes)

@igorlukanin

Copy link
Copy Markdown
Member Author

Good catch, and it was worse than "unverified". I built the section's own model with a rollup declaring granularity: month and generated the build SQL:

CREATE TABLE stb_pre_aggregations.orders_orders_by_retail_month AS
SELECT date_trunc('month', ("retail_calendar".date_value::timestamptz AT TIME ZONE 'UTC')) "retail_calendar__date_month", ...

date_trunc, not retail_month_begins. The rollup is matched and then serves the query, so a correctly-declared pre-aggregation on a calendar cube silently returns Gregorian months. Same on both planners.

The control that makes it conclusive: on the same model without the pre-aggregation, Tesseract does emit retail_calendar.retail_month_begins. So the override works for a plain query and is dropped when the same grouping is materialized.

That falsifies both claims the section made — the "declare it explicitly" advice does not protect the reader, so I replaced the section with a warning not to pre-aggregate these groupings at all. Also corrected an earlier ticket of mine that asserted the declared-granularity case worked; it doesn't, and that makes the defect wider than filed.

Comment thread docs-mintlify/recipes/data-modeling/custom-calendar.mdx Outdated
@igorlukanin
igorlukanin marked this pull request as draft September 3, 2026 12:47
@igorlukanin
igorlukanin marked this pull request as ready for review September 3, 2026 12:51
@claude

claude Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Claude finished @igorlukanin's task in 1m 56s —— View job


Approve — the correction is right; 2 low notes, all prior threads resolved

Full review of `441c1a6`
  • Diff 441c1a6 against the previous review
  • Verify the corrected pre-aggregation claim
  • Resolve my stale thread, dedupe against prior comments
  • Post updated review

The correction

Only 441c1a6 is new, and it reverses the 0ef1eee warning on both pages: a rollup that declares the overridden granularity is built from the overriding column and serves correctly; the failure is confined to hierarchy expansion from a finer rollup. The recipe also gains a concrete four-line rollup example.

Your re-run settles it, and it matches the trace I posted last round rather than contradicting it — the earlier date_trunc came from a probe that had constructed PostgresQuery without useNativeSqlPlanner, so both arms ran on the legacy planner. With the flag set, build emits retail_calendar.retail_month_begins. I re-checked the serve half in the current tree and it holds: BaseTimeDimension.dimensionSql returns super.dimensionSql() — the plain rollup column, no re-truncation — when context.rollupGranularity === this.granularityObj?.granularity (BaseTimeDimension.ts:114-117). So both halves of the declared-granularity path are now accounted for, and the section is as evidence-backed as the rest of the page.

The hierarchy finding is a good addition and worth keeping in the warning: your daymonth results show it breaks on both planners, silently on legacy and loudly on Tesseract (selecting retail_month_begins from a rollup table that only holds retail_calendar__date_day). Re-pointing CORE-856 at the matching path is the right call — the defect is in expandGranularity matching, not in the override.

One thing the warning leaves implicit that might be worth a clause: the reader can do everything right and still get hit, because a pre-existing day rollup on the same cube can be matched for a retail-month query. "Declare the overridden granularity rather than relying on a finer rollup" reads as advice about rollups you are about to write; the exposure is also from rollups already there. Your call — the mechanism is stated accurately either way.

Prior threads

All four now resolved. I resolved my own thread on the previous pre-aggregation wording, since 441c1a6 addresses it directly; the three from earlier rounds you had already closed. Nothing re-raised.

Findings

# Severity File Issue
1 Low recipes/data-modeling/custom-calendar.mdx:425 The only YAML-only snippet on the page, and the only fence without title= — no JavaScript counterpart
2 Low docs/data-modeling/concepts/calendar-cubes.mdx:488 Warning sits under ### Naming a granularity defined with sql but applies to the whole ## Overriding granularities section

Both are inline. Neither blocks merge.

· igor/core-855-docs-add-a-recipe-for-4-5-4-calendars-with-calendar-cubes ·

Comment thread docs-mintlify/recipes/data-modeling/custom-calendar.mdx Outdated
Comment thread docs-mintlify/docs/data-modeling/concepts/calendar-cubes.mdx Outdated
@claude

claude Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Claude finished @igorlukanin's task in 1m 18s —— View job


Approve — all threads now resolved; no new findings, 1 whitespace nit

Full review of `d58a409`
  • Diff d58a409 against the previous review
  • Check whether the 2 open threads are addressed
  • Verify the new JavaScript snippet against docs conventions
  • Resolve stale threads, post updated review

Prior threads — both closed

Prior finding Status
Low — Pre-aggregations block was YAML-only and the one fence without title= Fixed. Wrapped in <CodeGroup> with a JavaScript counterpart; both fences now carry title=, so all 14 fences on the page are consistent (custom-calendar.mdx:427,440)
Low — warning scoped under ### Naming a granularity defined with sql Fixed. Moved to calendar-cubes.mdx:452, which sits after the ## Overriding granularities body (heading at 324) and before ### Naming… (466). A reader who stops at the section's own example now sees it

Both resolved.

The new JavaScript snippet, checked

measures: [count] and time_dimension: retail_calendar.date are the forms used everywhere else in the docs — using-pre-aggregations.mdx:86, getting-started-pre-aggregations.mdx:185, lambda-pre-aggregations.mdx:98 — so the bare identifiers will be picked up as references by the transpiler rather than read as globals. The cross-cube time_dimension is backed by the join defined earlier on the page, so the snippet is complete as printed.

The anchor the recipe links to, #naming-a-granularity-defined-with-sql, is unaffected by the move: the heading itself didn't change, only what precedes it.

Nit

calendar-cubes.mdx:499-501 — the warning's removal from the bottom of the page left three consecutive blank lines before the link-reference block. Harmless in the rendered output, invisible to the reader, and I mentioned it in the now-resolved thread; noting it here only so it isn't lost.

· [`igor/core-855-docs-add-a-recipe-for-4-5-4-calendars-with-calendar-cubes`](https://github.com/cube-js/cube/tree/igor/core-855-docs-add-a-recipe-for-4-5-4-calendars-with-calendar-cubes)

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.

2 participants