diff --git a/docs-mintlify/docs.json b/docs-mintlify/docs.json
index 724945f33bb93..ce4e970f9f1b1 100644
--- a/docs-mintlify/docs.json
+++ b/docs-mintlify/docs.json
@@ -515,6 +515,7 @@
"group": "DAX API",
"root": "reference/core-data-apis/dax-api/index",
"pages": [
+ "reference/core-data-apis/dax-api/cross-view-filter",
"reference/core-data-apis/dax-api/reference"
]
}
diff --git a/docs-mintlify/reference/core-data-apis/dax-api/cross-view-filter.mdx b/docs-mintlify/reference/core-data-apis/dax-api/cross-view-filter.mdx
new file mode 100644
index 0000000000000..fddf78c726cda
--- /dev/null
+++ b/docs-mintlify/reference/core-data-apis/dax-api/cross-view-filter.mdx
@@ -0,0 +1,88 @@
+---
+title: Cross-view filtering
+description: "Enable cross-view filtering in the DAX API to build dashboards that span multiple views in Power BI."
+---
+
+By default, the [DAX API][ref-dax-api] exposes each [view][ref-views] as a
+separate perspective in [Power BI][ref-powerbi]. Visualizations from different
+views can't share the same filters, and a single report can't combine measures
+and dimensions across views.
+
+Cross-view filtering removes this limitation, letting you build dashboards
+that span multiple views and apply filters consistently across visualizations.
+It is supported in both [Live connection](#live-connection-mode) and
+[DirectQuery](#directquery-mode) modes.
+
+To use cross-view filters such as slicers, you must enable single-perspective mode
+in the DAX API. To enable single-perspective mode in the DAX API,
+set the `CUBEJS_DAX_SINGLE_PERSPECTIVE` environment variable to `true`.
+
+In single-perspective mode, all views are exposed as part of a single perspective.
+This allows you to use a single connection and show visualizations from different
+views on the same dashboard.
+
+
+
+Single-perspective mode changes the way views are exposed by the DAX API.
+Because of that, it is not backwards-compatible with dashboards created before
+enabling this environment variable.
+
+
+
+## Live connection mode
+
+In Live connection mode, cross-view filters work automatically, provided that
+the following two conditions are met:
+
+- The column used in the filter must be present in every view that the
+cross-filter has to be applied to, and named exactly the same in each view.
+- The column in all such views must resolve to the same cube member.
+
+For example, if views `orders_view` and `users_view` both expose a column named
+`address_country` pointing to the `country.country` cube member, cross-filtering
+will be applied across these views.
+
+If the `address_country` column in `orders_view` points to `orders.country`
+while the `address_country` column in `users_view` points to `users.country`,
+cross-filtering will not be applied, because the columns resolve to different
+cube members.
+
+Likewise, if `orders_view` exposes an `address_country` column pointing to
+`country.country` and `users_view` exposes a `country` column also pointing to
+`country.country`, cross-filtering will not be applied, because the column
+names differ between the views.
+
+## DirectQuery mode
+
+In DirectQuery mode, cross-view filters require the same two conditions as in
+[Live connection mode](#live-connection-mode): the filter column must be
+present in every view with the exact same name, and it must resolve to the
+same cube member across those views.
+
+In addition, you must manually configure relationships between tables in Power
+BI so that filters propagate across views:
+
+- Open **Model view** in the left sidebar.
+- On the right, open the **Data** sidebar, then the **Model** tab.
+- Right-click **Relationships** and choose **New relationship**.
+- In the **Properties** sidebar, select a table and a column.
+- For cardinality, choose **Many to many (\*:\*)**. Ignore the warning.
+- Select another table with the same column.
+- For cross-filter direction, select **Both**.
+- Click **Apply changes**.
+
+Once the relationship is configured correctly, cross-view filters will work in
+DirectQuery mode.
+
+The steps above describe configuring a relationship between two tables. When
+many views need to be cross-filtered, the best practice is to create a single
+view in the Cube data model that includes all the columns from the views that
+need to be cross-filtered, and then create relationships between this large
+view and each of the other views. This keeps the relationship graph in Power
+BI simple and avoids configuring pairwise relationships between every
+combination of views.
+
+
+[ref-dax-api]: /reference/core-data-apis/dax-api
+[ref-views]: /docs/data-modeling/views
+[ref-powerbi]: /admin/connect-to-data/visualization-tools/powerbi
diff --git a/docs-mintlify/reference/core-data-apis/dax-api/index.mdx b/docs-mintlify/reference/core-data-apis/dax-api/index.mdx
index 99981ff9b7ac1..a1e49ed4468ae 100644
--- a/docs-mintlify/reference/core-data-apis/dax-api/index.mdx
+++ b/docs-mintlify/reference/core-data-apis/dax-api/index.mdx
@@ -73,8 +73,20 @@ The DAX API only exposes [views][ref-views], not cubes.
+## Cross-view filtering
+
+By default, each view is exposed as a separate perspective in Power BI, so
+visualizations from different views can't share the same filters. Cross-view
+filtering lets you build dashboards that span multiple views and apply filters
+consistently across visualizations, in both Live connection and DirectQuery
+modes.
+
+See [Cross-view filtering][ref-cross-view-filter] for details on how to enable
+and use it.
+
[ref-powerbi]: /admin/connect-to-data/visualization-tools/powerbi
+[ref-cross-view-filter]: /reference/core-data-apis/dax-api/cross-view-filter
[link-dax]: https://learn.microsoft.com/en-us/dax/
[ref-sql-api]: /reference/sql-api
[ref-ref-dax-api]: /reference/dax-api/reference
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/base_tools.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/base_tools.rs
index a80f802569558..a784f4ad24a71 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/base_tools.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/base_tools.rs
@@ -13,6 +13,11 @@ use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;
+/// Dialect-independent callbacks to the JavaScript side, used
+/// during compilation and planning: SQL templates, time-series
+/// generation, allocated params, pre-aggregation lookup, join-tree
+/// resolution. Dialect-specific helpers live behind `DriverTools`,
+/// reachable via `driver_tools()`.
#[nativebridge::native_bridge]
pub trait BaseTools {
fn driver_tools(&self, external: bool) -> Result, CubeError>;
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/driver_tools.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/driver_tools.rs
index d11cd5c8ad073..2bb6cd8eff12a 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/driver_tools.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/driver_tools.rs
@@ -8,6 +8,10 @@ use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;
+/// Dialect-specific SQL helpers implemented by the JS driver. Used
+/// only at SQL-generation time, once the target dialect is known:
+/// timezone conversion, granularity-grouped columns, HLL functions,
+/// interval arithmetic, type casts.
#[nativebridge::native_bridge]
pub trait DriverTools {
fn convert_tz(&self, field: String) -> Result;
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/member_sql.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/member_sql.rs
index f913103fff43a..97fcc5545c2eb 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/member_sql.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/member_sql.rs
@@ -16,6 +16,9 @@ use cubenativeutils::CubeError;
use std::rc::Rc;
use std::{any::Any, cell::RefCell, rc::Weak};
+/// Result of evaluating a member's `sql` JS function: a single SQL
+/// string, or — for pre-aggregation `dimensions:` / `measures:`
+/// reference lists — one string per referenced member.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SqlTemplate {
String(String),
@@ -40,6 +43,9 @@ impl NativeDeserialize for SqlTemplate {
}
}
+/// Column argument passed to
+/// `FILTER_PARAMS.cube.member.filter(...)`: either a plain column
+/// name string, or a JS callback that produces the SQL snippet.
#[derive(Clone)]
pub enum FilterParamsColumn {
String(String),
@@ -179,6 +185,9 @@ pub struct SecutityContextProps {
pub values: Vec,
}
+/// Dependencies collected while compiling a member `sql` function.
+/// Each `{arg:N}` / `{fp:N}` / `{fg:N}` / `{sv:N}` placeholder in
+/// the produced `SqlTemplate` indexes into one of these vectors.
#[derive(Default, Clone, Debug)]
pub struct SqlTemplateArgs {
pub symbol_paths: Vec>,
@@ -296,6 +305,11 @@ impl ProxyStateWeak {
}
}
+/// A member's `sql:` function as provided by the JS schema compiler.
+/// `compile_template_sql` invokes the function under proxied
+/// arguments (`{CUBE}`, `FILTER_PARAMS`, `FILTER_GROUP`,
+/// `SECURITY_CONTEXT`, `SQL_UTILS`) and returns the resulting SQL
+/// template together with the dependencies the function touched.
pub trait MemberSql {
fn args_names(&self) -> &Vec;
fn as_any(self: Rc) -> Rc;
@@ -306,6 +320,12 @@ pub trait MemberSql {
) -> Result<(SqlTemplate, SqlTemplateArgs), CubeError>;
}
+/// Neon-backed implementation of `MemberSql`. `compile_template_sql`
+/// calls the JS function with proxy objects that record every
+/// accessed member path, `FILTER_PARAMS` / `FILTER_GROUP` call, and
+/// `SECURITY_CONTEXT.x.filter(...)` / `unsafeValue()` reference into
+/// a shared state, then returns the produced template together with
+/// that state as `SqlTemplateArgs`.
pub struct NativeMemberSql {
native_object: NativeObjectHandle,
args_names: Vec,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/mod.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/mod.rs
index 508c2841db880..c9d878012c283 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/mod.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/mod.rs
@@ -1,3 +1,10 @@
+//! DTO layer for talking to the JavaScript side of Cube.
+//!
+//! Every type here mirrors the shape of an object delivered by the
+//! schema compiler — cube and member definitions, filter params,
+//! callbacks, security context, and so on. Tesseract reads these
+//! types as input; no business logic lives here.
+
pub mod base_query_options;
pub mod base_tools;
pub mod case_definition;
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/security_context.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/security_context.rs
index 95630b44f3035..6f664253222c7 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/security_context.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/security_context.rs
@@ -5,5 +5,8 @@ use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;
+/// Type-erased reference to the JS-side security context object.
+/// Used to materialise `SECURITY_CONTEXT.x.filter(...)` /
+/// `unsafeValue()` proxies when compiling a member SQL function.
#[nativebridge::native_bridge]
pub trait SecurityContext {}
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/sql_templates_render.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/sql_templates_render.rs
index c28d6bd26494a..c9deac641a914 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/sql_templates_render.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/sql_templates_render.rs
@@ -6,6 +6,9 @@ use minijinja::{value::Value, Environment};
use std::collections::HashMap;
use std::marker::PhantomData;
+/// Jinja2 template rendering for SQL templates. The native
+/// implementation keeps the parsed templates locally and renders
+/// without crossing the JS boundary.
pub trait SqlTemplatesRender {
fn contains_template(&self, template_name: &str) -> bool;
fn render_template(&self, name: &str, ctx: Value) -> Result;
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/sql_utils.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/sql_utils.rs
index 84d78dd137e34..f56954ea9e5d8 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/sql_utils.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/sql_utils.rs
@@ -5,5 +5,8 @@ use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;
+/// Opaque holder for the JS-side `SQL_UTILS` object. Tesseract only
+/// forwards it back into member `sql` functions; the methods
+/// invoked on it are a JS concern.
#[nativebridge::native_bridge]
pub trait SqlUtils {}
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/aggregate_multiplied_subquery.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/aggregate_multiplied_subquery.rs
index d0f8af0350a46..85ef84e7b2c05 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/aggregate_multiplied_subquery.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/aggregate_multiplied_subquery.rs
@@ -5,6 +5,12 @@ use std::rc::Rc;
logical_source_enum!(AggregateMultipliedSubquerySource, [Cube, MeasureSubquery]);
+/// Subquery that aggregates a multiplied measure: a `keys_subquery`
+/// produces the unique key set, a `source` (cube or
+/// `MeasureSubquery`) supplies the values, optional
+/// `dimension_subqueries` materialise sub-query dimensions, and
+/// `pre_aggregation_override` lets a matched pre-aggregation
+/// short-circuit the whole CTE.
pub struct AggregateMultipliedSubquery {
pub schema: Rc,
pub keys_subquery: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/calc_groups_cross_join.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/calc_groups_cross_join.rs
deleted file mode 100644
index 8d96a50d65b1f..0000000000000
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/calc_groups_cross_join.rs
+++ /dev/null
@@ -1,85 +0,0 @@
-use crate::planner::MemberSymbol;
-
-use super::*;
-use cubenativeutils::CubeError;
-use std::rc::Rc;
-use typed_builder::TypedBuilder;
-
-#[derive(Clone)]
-pub struct CalcGroupDescription {
- pub symbol: Rc,
- pub values: Vec,
-}
-
-impl PrettyPrint for CalcGroupDescription {
- fn pretty_print(&self, result: &mut PrettyPrintResult, state: &PrettyPrintState) {
- let state = state.new_level();
- result.println(
- &format!("{}:{}", self.symbol.full_name(), self.values.join(", ")),
- &state,
- );
- }
-}
-
-#[derive(Clone, TypedBuilder)]
-pub struct CalcGroupsCrossJoin {
- source: BaseQuerySource,
- calc_groups: Vec>,
-}
-
-impl CalcGroupsCrossJoin {
- pub fn source(&self) -> &BaseQuerySource {
- &self.source
- }
-
- pub fn calc_groups(&self) -> &Vec> {
- &self.calc_groups
- }
-}
-
-impl LogicalNode for CalcGroupsCrossJoin {
- fn as_plan_node(self: &Rc) -> PlanNode {
- PlanNode::CalcGroupsCrossJoin(self.clone())
- }
-
- fn inputs(&self) -> Vec {
- vec![self.source.as_plan_node()]
- }
-
- fn with_inputs(self: Rc, inputs: Vec) -> Result, CubeError> {
- check_inputs_len(&inputs, 1, self.node_name())?;
- let source = &inputs[0];
-
- Ok(Rc::new(Self {
- calc_groups: self.calc_groups.clone(),
- source: self.source.with_plan_node(source.clone())?,
- }))
- }
-
- fn node_name(&self) -> &'static str {
- "CalcGroupsCrossJoin"
- }
-
- fn try_from_plan_node(plan_node: PlanNode) -> Result, CubeError> {
- if let PlanNode::CalcGroupsCrossJoin(item) = plan_node {
- Ok(item)
- } else {
- Err(cast_error(&plan_node, "MultiStageGetDateRange"))
- }
- }
-}
-
-impl PrettyPrint for CalcGroupsCrossJoin {
- fn pretty_print(&self, result: &mut PrettyPrintResult, state: &PrettyPrintState) {
- let state = state.new_level();
- result.println("CaclGroupsCrossJoin: ", &state);
- let state = state.new_level();
- result.println("Groups: ", &state);
- let details_state = state.new_level();
- for group in &self.calc_groups {
- group.pretty_print(result, &details_state);
- }
- result.println("Source: ", &state);
- self.source.pretty_print(result, &details_state);
- }
-}
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/cube.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/cube.rs
index 746423d404626..dfd2f638bbb73 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/cube.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/cube.rs
@@ -4,6 +4,9 @@ use cubenativeutils::CubeError;
use std::rc::Rc;
use typed_builder::TypedBuilder;
+/// Marker for an "original SQL" pre-aggregation attached to a cube
+/// — the physical builder uses its name to substitute the cube's
+/// table expression with the matching pre-aggregation source.
#[derive(Clone, TypedBuilder)]
pub struct OriginalSqlPreAggregation {
name: String,
@@ -24,6 +27,9 @@ impl PrettyPrint for OriginalSqlPreAggregation {
}
}
+/// A cube referenced from the logical plan — wraps the planner's
+/// `BaseCube` and optionally pins a matching "original SQL"
+/// pre-aggregation as the cube's source.
#[derive(Clone, TypedBuilder)]
pub struct Cube {
cube: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/dimension_subquery.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/dimension_subquery.rs
index 5037e7b5419b8..aca02901ce637 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/dimension_subquery.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/dimension_subquery.rs
@@ -4,6 +4,10 @@ use crate::planner::MemberSymbol;
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// Materialisation of a `sub_query: true` dimension: a subquery
+/// that groups by the owning cube's primary keys and computes the
+/// dimension's measure expression, then is joined back to the host
+/// query on those keys.
pub struct DimensionSubQuery {
pub query: Rc,
pub primary_keys_dimensions: Vec>,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/filter.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/filter.rs
index 246b84a91208c..2cc4fe45c2b83 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/filter.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/filter.rs
@@ -2,6 +2,10 @@ use super::pretty_print::*;
use crate::planner::filter::{Filter, FilterItem};
use itertools::Itertools;
+/// All filters of a query split by what they target. WHERE-style
+/// filters (`time_dimensions_filters`, `dimensions_filters`,
+/// `segments`) are surfaced together via `all_filters`; HAVING-style
+/// `measures_filter` stays separate.
#[derive(Default)]
pub struct LogicalFilter {
pub dimensions_filters: Vec,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/full_key_aggregate.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/full_key_aggregate.rs
index 90323e39f952a..6d6a8d1fbaf57 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/full_key_aggregate.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/full_key_aggregate.rs
@@ -4,6 +4,8 @@ use cubenativeutils::CubeError;
use std::rc::Rc;
use typed_builder::TypedBuilder;
+/// Reference to a multi-stage CTE consumed by `FullKeyAggregate`:
+/// the CTE's name plus the symbols it exposes.
#[derive(TypedBuilder)]
pub struct MultiStageSubqueryRef {
name: String,
@@ -37,6 +39,10 @@ impl PrettyPrint for MultiStageSubqueryRef {
}
}
+/// Top-level aggregating source that stitches together several
+/// multi-stage / multi-fact CTEs into one keyed result. The
+/// physical builder picks a join strategy from `multi_stage_subquery_refs`
+/// and `use_full_join_and_coalesce`.
#[derive(Clone, TypedBuilder)]
pub struct FullKeyAggregate {
schema: Rc,
@@ -50,6 +56,9 @@ impl FullKeyAggregate {
&self.schema
}
+ /// When true, multi-fact branches are stitched together via a
+ /// FULL OUTER JOIN over keys with COALESCE on dimension columns;
+ /// otherwise an INNER JOIN is used.
pub fn use_full_join_and_coalesce(&self) -> bool {
self.use_full_join_and_coalesce
}
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/helper.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/helper.rs
index c9f12e751aa19..7292414da03cf 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/helper.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/helper.rs
@@ -4,6 +4,9 @@ use crate::planner::MemberSymbol;
use super::*;
use std::rc::Rc;
+/// All member symbols reachable from a schema and its filters —
+/// schema members concatenated with every member referenced in
+/// dimension, segment and measure filter trees.
pub fn all_symbols(schema: &Rc, filters: &LogicalFilter) -> Vec> {
let mut symbols = schema.all_members().cloned().collect_vec();
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/join.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/join.rs
index 7faecbcaf09a8..6910aa959bd29 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/join.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/join.rs
@@ -5,6 +5,8 @@ use cubenativeutils::CubeError;
use std::rc::Rc;
use typed_builder::TypedBuilder;
+/// One non-root cube of a `LogicalJoin`, with the SQL expression
+/// that joins it to the rest of the tree.
#[derive(Clone, TypedBuilder)]
pub struct LogicalJoinItem {
cube: Rc,
@@ -29,6 +31,9 @@ impl PrettyPrint for LogicalJoinItem {
}
}
+/// Join of cubes that backs a query source: a `root` cube plus
+/// non-root cubes (`joins`), optionally extended by sub-query
+/// dimensions that contribute their own joined-in CTEs.
#[derive(Clone, TypedBuilder)]
pub struct LogicalJoin {
#[builder(default)]
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/keys_subquery.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/keys_subquery.rs
index 05ab6254f8ddf..5b78c98fcc2bd 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/keys_subquery.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/keys_subquery.rs
@@ -4,6 +4,10 @@ use cubenativeutils::CubeError;
use std::rc::Rc;
use typed_builder::TypedBuilder;
+/// Subquery that produces the primary-key set of `pk_cube` after
+/// applying the query's filters. Used as the outer key set in
+/// `AggregateMultipliedSubquery`: a measure subquery aggregates
+/// values and is joined back against this set on the primary keys.
#[derive(Clone, TypedBuilder)]
pub struct KeysSubQuery {
pk_cube: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/logical_node.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/logical_node.rs
index 3cb7b557a1b92..040be06612757 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/logical_node.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/logical_node.rs
@@ -2,6 +2,9 @@ use super::*;
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// Node of the logical-plan tree. Exposes its child nodes through
+/// `inputs()` / `with_inputs()` so generic passes can walk and
+/// rewrite the tree without knowing concrete node types.
pub trait LogicalNode {
fn inputs(&self) -> Vec;
@@ -14,6 +17,9 @@ pub trait LogicalNode {
fn node_name(&self) -> &'static str;
}
+/// Type-erased handle for a logical-plan node. Generic traversal
+/// works in terms of `PlanNode`; concrete typed access is recovered
+/// via `into_logical_node` / each node's `try_from_plan_node`.
#[derive(Clone)]
pub enum PlanNode {
Query(Rc),
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/logical_query_modifers.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/logical_query_modifers.rs
index 2c78e45b7966f..3f2ac64831b67 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/logical_query_modifers.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/logical_query_modifers.rs
@@ -1,6 +1,8 @@
use super::*;
use crate::planner::query_properties::OrderByItem;
+/// Per-query modifiers that sit outside the result schema: paging,
+/// ordering, and the ungrouped flag.
pub struct LogicalQueryModifiers {
pub offset: Option,
pub limit: Option,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/logical_source.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/logical_source.rs
index 09fa35796e1ed..8a8b2af935ebe 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/logical_source.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/logical_source.rs
@@ -1,6 +1,10 @@
use super::*;
use cubenativeutils::CubeError;
+/// Enum-of-children adapter used by nodes that accept one of several
+/// `LogicalNode` types as their source. Lets the generic `with_inputs`
+/// machinery rebuild the source from a freshly transformed `PlanNode`
+/// while keeping the variant unchanged.
pub trait LogicalSource: Sized + PrettyPrint {
fn as_plan_node(&self) -> PlanNode;
fn with_plan_node(&self, plan_node: PlanNode) -> Result;
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/measure_subquery.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/measure_subquery.rs
index 2e99dc5c7681d..518702de43221 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/measure_subquery.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/measure_subquery.rs
@@ -2,6 +2,9 @@ use super::*;
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// Inner aggregating subquery for a measure inside a multiplied
+/// aggregate flow — one of the two `AggregateMultipliedSubquerySource`
+/// variants (the other being a raw `Cube`).
pub struct MeasureSubquery {
pub schema: Rc,
pub source: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/mod.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/mod.rs
index 95e6aab42c5ed..b7903fea5598c 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/mod.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/mod.rs
@@ -1,3 +1,10 @@
+//! Logical representation of a query as a tree of `PlanNode`s.
+//!
+//! Each node implements `LogicalNode` (children via `inputs()` /
+//! `with_inputs()`, plus a name for diagnostics). The tree is built
+//! by the planner and consumed by `physical_plan_builder`, which
+//! turns it into a `QueryPlan`. No SQL is produced here.
+
#[macro_use]
mod logical_source;
mod aggregate_multiplied_subquery;
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/calculation.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/calculation.rs
index 2e8e8b2783fa0..50d2f477a360d 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/calculation.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/calculation.rs
@@ -6,6 +6,8 @@ use itertools::Itertools;
use std::rc::Rc;
use typed_builder::TypedBuilder;
+/// Semantic category of a multi-stage measure CTE — drives how the
+/// physical builder shapes the rendered expression.
#[derive(PartialEq, Clone)]
pub enum MultiStageCalculationType {
Rank,
@@ -23,6 +25,8 @@ impl ToString for MultiStageCalculationType {
}
}
+/// Which SQL window-function flavour, if any, the calculation
+/// renders as.
#[derive(PartialEq, Clone)]
pub enum MultiStageCalculationWindowFunction {
Rank,
@@ -40,6 +44,9 @@ impl ToString for MultiStageCalculationWindowFunction {
}
}
+/// Measure CTE in a multi-stage chain — wraps a `FullKeyAggregate`
+/// source with the partition / window function / ordering decided
+/// by `calculation_type`.
#[derive(TypedBuilder)]
pub struct MultiStageMeasureCalculation {
schema: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/dimension.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/dimension.rs
index 5402942163270..279412a2914c3 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/dimension.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/dimension.rs
@@ -7,6 +7,8 @@ use itertools::Itertools;
use std::rc::Rc;
use typed_builder::TypedBuilder;
+/// Dimension CTE in a multi-stage chain — materialises a
+/// multi-stage dimension on top of a `FullKeyAggregate` source.
#[derive(TypedBuilder)]
pub struct MultiStageDimensionCalculation {
schema: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/get_date_range.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/get_date_range.rs
index 76e7ebfcf2fe4..ecf5abe58a2df 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/get_date_range.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/get_date_range.rs
@@ -3,6 +3,9 @@ use crate::planner::MemberSymbol;
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// CTE that resolves the actual date range of a time dimension at
+/// query time (used by rolling windows when no literal range is
+/// given).
pub struct MultiStageGetDateRange {
pub time_dimension: Rc,
pub source: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/leaf_measure.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/leaf_measure.rs
index 95d4c2989f432..09719fab65ce4 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/leaf_measure.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/leaf_measure.rs
@@ -4,6 +4,10 @@ use crate::planner::MemberSymbol;
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// Leaf CTE of a multi-stage chain — a base query that produces the
+/// raw aggregated values feeding the rest of the chain. Optional
+/// state rendering (`render_measure_as_state`) and time shifts
+/// happen here.
pub struct MultiStageLeafMeasure {
pub measures: Vec>,
pub render_measure_as_state: bool, //Render measure as state, for example hll state for count_approx
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/member.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/member.rs
index bd436b65c97ea..fcd81fd9ecb74 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/member.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/member.rs
@@ -2,6 +2,8 @@ use crate::logical_plan::*;
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// Body of a `LogicalMultiStageMember` — one of the multi-stage
+/// CTE shapes the planner can produce.
pub enum MultiStageMemberLogicalType {
LeafMeasure(Rc),
MultipliedMeasure(Rc),
@@ -54,6 +56,8 @@ impl PrettyPrint for MultiStageMemberLogicalType {
}
}
+/// Named CTE in a multi-stage chain. `Query.multistage_members`
+/// holds one per CTE the source depends on.
pub struct LogicalMultiStageMember {
pub name: String,
pub member_type: MultiStageMemberLogicalType,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/rolling_window.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/rolling_window.rs
index 367e08aea54f6..f35661f81342c 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/rolling_window.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/rolling_window.rs
@@ -5,6 +5,8 @@ use crate::planner::MemberSymbol;
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// Regular rolling window: trailing and/or leading bounds, plus a
+/// time-series offset.
pub struct MultiStageRegularRollingWindow {
pub trailing: Option,
pub leading: Option,
@@ -25,6 +27,8 @@ impl PrettyPrint for MultiStageRegularRollingWindow {
}
}
+/// `to_date` rolling window — bounded by the start of the
+/// specified granularity (month-to-date, year-to-date, …).
pub struct MultiStageToDateRollingWindow {
pub granularity_obj: Rc,
}
@@ -40,6 +44,8 @@ impl PrettyPrint for MultiStageToDateRollingWindow {
}
}
+/// Flavour of rolling-window calculation: regular trailing/leading
+/// window, `to_date` window, or a running-total accumulation.
pub enum MultiStageRollingWindowType {
Regular(MultiStageRegularRollingWindow),
ToDate(MultiStageToDateRollingWindow),
@@ -58,6 +64,9 @@ impl PrettyPrint for MultiStageRollingWindowType {
}
}
+/// Rolling-window CTE — combines a time-series CTE (the date axis)
+/// with a measure CTE and applies the chosen rolling computation
+/// to each point on the series.
pub struct MultiStageRollingWindow {
pub schema: Rc,
pub is_ungrouped: bool,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/time_series.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/time_series.rs
index bb0cf5cc42608..f11be1823f9d3 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/time_series.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/time_series.rs
@@ -4,6 +4,10 @@ use cubenativeutils::CubeError;
use std::rc::Rc;
use typed_builder::TypedBuilder;
+/// Date-axis CTE for a rolling window — generates the series of
+/// points the rolling computation walks over. The range comes
+/// either from a literal `date_range` or from a sibling
+/// `MultiStageGetDateRange` CTE (`get_date_range_multistage_ref`).
#[derive(TypedBuilder)]
pub struct MultiStageTimeSeries {
time_dimension: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/measure_matcher.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/measure_matcher.rs
index 9869fe47db6aa..1651b47a5fc56 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/measure_matcher.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/measure_matcher.rs
@@ -5,20 +5,20 @@ use std::collections::HashSet;
use std::rc::Rc;
pub struct MeasureMatcher {
- only_addictive: bool,
+ only_additive: bool,
pre_aggregation_measures: HashSet,
matched_measures: HashSet,
}
impl MeasureMatcher {
- pub fn new(pre_aggregation: &CompiledPreAggregation, only_addictive: bool) -> Self {
+ pub fn new(pre_aggregation: &CompiledPreAggregation, only_additive: bool) -> Self {
let pre_aggregation_measures = pre_aggregation
.measures
.iter()
.map(|m| m.full_name())
.collect();
Self {
- only_addictive,
+ only_additive,
pre_aggregation_measures,
matched_measures: HashSet::new(),
}
@@ -38,7 +38,7 @@ impl MeasureMatcher {
return Ok(false);
}
if self.pre_aggregation_measures.contains(&measure.full_name())
- && (!self.only_addictive || measure.is_addictive())
+ && (!self.only_additive || measure.is_additive())
{
self.matched_measures.insert(measure.full_name());
return Ok(true);
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/optimizer.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/optimizer.rs
index 6f1f71f8787b4..7d939319c307c 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/optimizer.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/optimizer.rs
@@ -504,9 +504,9 @@ impl PreAggregationOptimizer {
&self,
measures: &Vec>,
pre_aggregation: &CompiledPreAggregation,
- only_addictive: bool,
+ only_additive: bool,
) -> Result>, CubeError> {
- let mut matcher = MeasureMatcher::new(pre_aggregation, only_addictive);
+ let mut matcher = MeasureMatcher::new(pre_aggregation, only_additive);
for measure in measures.iter() {
if !matcher.try_match(measure)? {
return Ok(None);
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/pre_aggregation.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/pre_aggregation.rs
index 18c33982f527a..e3f0a0d4893dc 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/pre_aggregation.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/pre_aggregation.rs
@@ -5,6 +5,10 @@ use itertools::Itertools;
use std::{collections::HashMap, rc::Rc};
use typed_builder::TypedBuilder;
+/// A matched pre-aggregation usable as a query source — its table
+/// (or rollup join / union), the members it exposes and its
+/// granularity. The physical builder reads members through the
+/// `*_refererences` helpers.
#[derive(TypedBuilder)]
pub struct PreAggregation {
name: String,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/query.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/query.rs
index 62abb99fd23b8..c0ddeacf9116b 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/query.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/query.rs
@@ -3,6 +3,10 @@ use cubenativeutils::CubeError;
use std::rc::Rc;
use typed_builder::TypedBuilder;
+/// Root node of a query in the logical plan: the data `source`
+/// (join / aggregate / pre-aggregation), its output `schema`,
+/// `filter` tree, query-level `modifers` (limit / offset / order /
+/// ungrouped), and the multi-stage CTEs the source depends on.
#[derive(Clone, TypedBuilder)]
pub struct Query {
#[builder(default)]
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/query_source.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/query_source.rs
index 88c163a617928..1b87d04d6cf39 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/query_source.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/query_source.rs
@@ -2,4 +2,7 @@ use super::*;
use cubenativeutils::CubeError;
use std::rc::Rc;
+// Top-level source of a `Query`: a plain `LogicalJoin`, an aggregated
+// `FullKeyAggregate` (for multi-stage / multi-fact rewrites), or a
+// matched `PreAggregation`.
logical_source_enum!(QuerySource, [LogicalJoin, FullKeyAggregate, PreAggregation]);
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/schema.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/schema.rs
index 7a1e1a419eb01..0a11a28aab067 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/schema.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/schema.rs
@@ -8,6 +8,9 @@ use std::collections::HashSet;
use std::fmt;
use std::rc::Rc;
+/// Output shape of a logical-plan node: the dimensions, time
+/// dimensions and measures it exposes, plus the full names of
+/// measures that need multiplied-aggregate handling downstream.
#[derive(Default, Clone)]
pub struct LogicalSchema {
pub time_dimensions: Vec>,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/auto_prefix.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/auto_prefix.rs
index 8fd8350fbec90..5f77d862ddffe 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/auto_prefix.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/auto_prefix.rs
@@ -11,6 +11,9 @@ use std::any::Any;
use std::collections::HashMap;
use std::rc::Rc;
+/// Prefixes a bare member-name SQL fragment with its cube's quoted
+/// alias (e.g. `id` → `"users".id`). Skips fragments that are not a
+/// single identifier — those already carry their own qualification.
pub struct AutoPrefixSqlNode {
input: Rc,
cube_references: HashMap,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/calendar_time_shift.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/calendar_time_shift.rs
index c421ddee3a33f..a6bea18c29d5f 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/calendar_time_shift.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/calendar_time_shift.rs
@@ -9,6 +9,10 @@ use std::any::Any;
use std::collections::HashMap;
use std::rc::Rc;
+/// Applies a calendar-cube time shift: when the dimension's
+/// resolved time-shift primary key matches an entry in `shifts`,
+/// renders the shifted reference (interval / named slot / custom
+/// SQL) declared on the calendar cube.
pub struct CalendarTimeShiftSqlNode {
shifts: HashMap, // Key is the full pk name of the calendar cube
input: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/case.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/case.rs
index fc8ac0d0d1ac3..ae5c3e7d2dfc9 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/case.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/case.rs
@@ -8,6 +8,10 @@ use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;
+/// Renders members defined via a `case:` body — both the classic
+/// `CASE WHEN cond THEN label …` and the switch-style
+/// `CASE switch WHEN value THEN sql …`. Members without a case body
+/// pass through to `input`.
pub struct CaseSqlNode {
input: Rc,
}
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/evaluate_sql.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/evaluate_sql.rs
index 8871b58aa1952..68a6d6ddb3580 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/evaluate_sql.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/evaluate_sql.rs
@@ -8,6 +8,9 @@ use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;
+/// Base case of the SQL-node chain: invokes the member symbol's own
+/// `to_sql` to produce the raw SQL fragment. All wrapping nodes
+/// eventually delegate down to this.
pub struct EvaluateSqlNode {}
impl EvaluateSqlNode {
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/factory.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/factory.rs
index bfcad5b6fe073..58e2e3473f2ca 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/factory.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/factory.rs
@@ -13,6 +13,10 @@ use crate::planner::symbols::CalendarDimensionTimeShift;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
+/// Builds the SQL-node chain for a query. Carries all the flags and
+/// reference maps the query needs (time shifts, render references,
+/// pre-aggregation refs, multi-stage partitions, etc.) and assembles
+/// them into a layered `SqlNode` via `default_node_processor`.
#[derive(Clone, Default)]
pub struct SqlNodesFactory {
time_shifts: TimeShiftState,
@@ -150,6 +154,14 @@ impl SqlNodesFactory {
)
}
+ /// Assembles the full SQL-node chain for the configured options.
+ /// Three sub-chains hang off a `RootSqlNode` keyed by member
+ /// kind: a dimension chain (geo / case / time-shift / calendar
+ /// time-shift wraps), a time-dimension chain, and a measure
+ /// chain (case → measure filter → final-measure / ungrouped /
+ /// multi-stage wraps → mask). The whole tree is then wrapped in
+ /// a top-level `RenderReferencesSqlNode` for query-wide reference
+ /// substitution.
pub fn default_node_processor(&self) -> Rc {
let evaluate_sql_processor = MaskedSqlNode::new(EvaluateSqlNode::new());
let auto_prefix_processor = AutoPrefixSqlNode::new(
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/final_measure.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/final_measure.rs
index 71c6579ec41e7..93420c4825bde 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/final_measure.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/final_measure.rs
@@ -9,6 +9,11 @@ use std::any::Any;
use std::collections::HashSet;
use std::rc::Rc;
+/// Applies the final aggregation wrap to a measure (sum / avg /
+/// count_distinct / pass-through, etc.) using `MeasureKind::aggregate_wrap`.
+/// Tracks measures that were classified as multiplied so the wrap
+/// switches to a distinct-count form, and routes
+/// `count_distinct_approx` through an HLL state when requested.
pub struct FinalMeasureSqlNode {
input: Rc,
rendered_as_multiplied_measures: HashSet,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/final_pre_aggregation_measure.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/final_pre_aggregation_measure.rs
index e650233c4b46c..1faa974a86c20 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/final_pre_aggregation_measure.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/final_pre_aggregation_measure.rs
@@ -10,6 +10,10 @@ use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;
+/// Substitutes a measure with the matching pre-aggregation column
+/// reference (rolled up via the measure's `pre_aggregate_wrap`),
+/// or falls through to `input` when the measure has no
+/// pre-aggregation entry.
pub struct FinalPreAggregationMeasureSqlNode {
input: Rc,
references: RenderReferences,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/geo_dimension.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/geo_dimension.rs
index f09bac3323cfd..45847de76343c 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/geo_dimension.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/geo_dimension.rs
@@ -8,6 +8,9 @@ use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;
+/// Renders a `Geo` dimension by combining its latitude and
+/// longitude SQL expressions into a paired projection. Pass-through
+/// for non-geo dimensions.
pub struct GeoDimensionSqlNode {
input: Rc,
}
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/masked.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/masked.rs
index af28236221bc2..2c2c9d312f762 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/masked.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/masked.rs
@@ -9,6 +9,10 @@ use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;
+/// Intercepts rendering for members masked by query-tools and
+/// substitutes the configured `mask_sql` expression. When a mask
+/// filter is set, wraps the result in a `CASE WHEN filter THEN
+/// original ELSE mask END`. Pass-through for non-masked members.
pub struct MaskedSqlNode {
input: Rc,
ungrouped: bool,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/measure_filter.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/measure_filter.rs
index 2488df55e1173..256cbfce6d793 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/measure_filter.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/measure_filter.rs
@@ -7,6 +7,9 @@ use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;
+/// Wraps a measure's rendered SQL in the `measure_filters` declared
+/// on its data-model definition (e.g. `FILTER (WHERE ...)` / CASE
+/// gating depending on the dialect templates).
pub struct MeasureFilterSqlNode {
input: Rc,
}
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/mod.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/mod.rs
index 35f81ee5d4708..74ffbf7fdf481 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/mod.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/mod.rs
@@ -1,3 +1,11 @@
+//! Chain of SQL-rendering nodes assembled by `SqlNodesFactory` and
+//! consumed by `SqlEvaluatorVisitor`. Each node decides — for a
+//! given `MemberSymbol` — whether to handle the rendering itself
+//! or to delegate to its `input`, so the chain progressively wraps
+//! the base SQL with cube prefixing, parenthesisation, CASE
+//! expressions, masking, aggregation, window functions, and
+//! pre-aggregation reference substitution.
+
pub mod auto_prefix;
pub mod calendar_time_shift;
pub mod case;
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/multi_stage_rank.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/multi_stage_rank.rs
index b290402215eac..46404fdd0f199 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/multi_stage_rank.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/multi_stage_rank.rs
@@ -8,6 +8,8 @@ use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;
+/// Renders a `Rank` measure as a SQL window function partitioned
+/// by `partition`. Non-rank measures go through `else_processor`.
pub struct MultiStageRankNode {
else_processor: Rc,
partition: Vec,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/multi_stage_window.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/multi_stage_window.rs
index 2738e1788e8cd..66806445ca297 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/multi_stage_window.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/multi_stage_window.rs
@@ -7,6 +7,10 @@ use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;
+/// Wraps a measure as a SQL window function partitioned by
+/// `partition`. Used for multi-stage measures whose partition is
+/// narrower than the full dimension set. Non-window measures go
+/// through `else_processor`.
pub struct MultiStageWindowNode {
input: Rc,
else_processor: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/render_references.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/render_references.rs
index 8657a573dd8c8..c681f0fe91174 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/render_references.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/render_references.rs
@@ -12,6 +12,9 @@ use std::rc::Rc;
#[derive(Clone)]
pub struct RawReferenceValue(pub String);
+/// Replacement form for a member that is rendered as a reference
+/// instead of being evaluated: a qualified column, a quoted string
+/// literal, or a raw SQL fragment.
#[derive(Clone)]
pub enum RenderReferencesType {
QualifiedColumnName(QualifiedColumnName),
@@ -37,6 +40,8 @@ impl From for RenderReferencesType {
}
}
+/// `full_name → RenderReferencesType` map keyed by a member's
+/// `MemberSymbol::full_name`.
#[derive(Default, Clone)]
pub struct RenderReferences {
references: HashMap,
@@ -60,6 +65,8 @@ impl RenderReferences {
}
}
+/// Substitutes a member with a pre-allocated reference from
+/// `references`, falling through to `input` when no entry exists.
pub struct RenderReferencesSqlNode {
input: Rc,
references: RenderReferences,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/rolling_window.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/rolling_window.rs
index 0acc52dab3b07..77bea9c59666c 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/rolling_window.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/rolling_window.rs
@@ -8,6 +8,9 @@ use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;
+/// Renders cumulative measures (rolling window / running total)
+/// through `input`, delegating non-cumulative measures to
+/// `default_processor`.
pub struct RollingWindowNode {
input: Rc,
default_processor: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/root_processor.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/root_processor.rs
index 8d6abdc040a3d..aff9774f3010a 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/root_processor.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/root_processor.rs
@@ -7,6 +7,8 @@ use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;
+/// Dispatches rendering to a kind-specific sub-chain based on the
+/// member's variant: dimension / time dimension / measure / other.
pub struct RootSqlNode {
dimension_processor: Rc,
time_dimesions_processor: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/sql_node.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/sql_node.rs
index bbff4d082e20d..f04ab893ddd28 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/sql_node.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/sql_node.rs
@@ -7,6 +7,10 @@ use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;
+/// One link in the SQL-rendering chain. Given a `MemberSymbol` and
+/// the surrounding context, returns the rendered SQL — either by
+/// computing it directly or by delegating up the chain through
+/// `node_processor`.
pub trait SqlNode {
fn to_sql(
&self,
@@ -22,6 +26,8 @@ pub trait SqlNode {
fn childs(&self) -> Vec>;
}
+/// Specialised renderer for `{CUBE}` / `{TABLE}` placeholders that
+/// only need the cube's name.
pub trait CubeNameNode {
fn to_sql(&self, cube_name: &String) -> Result;
}
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/time_dimension.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/time_dimension.rs
index 9095b6b90528e..e22b4408b5bfe 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/time_dimension.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/time_dimension.rs
@@ -8,6 +8,10 @@ use std::any::Any;
use std::collections::HashSet;
use std::rc::Rc;
+/// Renders a time dimension: applies the granularity (predefined
+/// or calendar SQL) and timezone conversion, unless the dimension's
+/// full name is listed in `dimensions_with_ignored_timezone` (used
+/// for pre-aggregation column references).
pub struct TimeDimensionNode {
dimensions_with_ignored_timezone: HashSet,
input: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/time_shift.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/time_shift.rs
index da4e2d38da858..6516be32ad44b 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/time_shift.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/time_shift.rs
@@ -8,6 +8,9 @@ use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;
+/// Applies a per-dimension time shift to time dimensions whose
+/// full name is in `shifts`, by rendering the dimension expression
+/// shifted by the configured interval.
pub struct TimeShiftSqlNode {
shifts: TimeShiftState,
input: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/ungroupped_measure.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/ungroupped_measure.rs
index c3654e7f5de4f..6e63351e0f46d 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/ungroupped_measure.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/ungroupped_measure.rs
@@ -7,6 +7,9 @@ use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;
+/// Renders measures for ungrouped contexts (an ungrouped measure
+/// member-level flag) — leaves the inner SQL unwrapped instead of
+/// applying the usual aggregation.
pub struct UngroupedMeasureSqlNode {
input: Rc,
}
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/ungroupped_query_final_measure.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/ungroupped_query_final_measure.rs
index d3e828a407ba5..e8fa504552f51 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/ungroupped_query_final_measure.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/ungroupped_query_final_measure.rs
@@ -8,6 +8,9 @@ use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;
+/// Renders measures inside an ungrouped query. Count-like measures
+/// keep their counting wrap to stay correct without GROUP BY; other
+/// measures pass through without aggregation.
pub struct UngroupedQueryFinalMeasureSqlNode {
input: Rc,
}
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/processors/mod.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/processors/mod.rs
index 9b90542acc78b..01a7b2c998bd5 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/processors/mod.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/processors/mod.rs
@@ -13,4 +13,3 @@ mod multi_stage_time_series;
mod pre_aggregation;
mod query;
mod query_source;
-//mod calc_groups_cross_join;
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/compiler.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/compiler.rs
index 9d8ae3f9b4b36..8c40103760673 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/compiler.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/compiler.rs
@@ -1,14 +1,12 @@
-use super::collectors::JoinHintsCollector;
use super::symbols::{MemberExpressionExpression, MemberExpressionSymbol, MemberSymbol};
use super::SymbolPath;
use super::SymbolPathType;
use super::{
CubeNameSymbol, CubeNameSymbolFactory, CubeTableSymbol, CubeTableSymbolFactory,
- DimensionSymbolFactory, MeasureSymbolFactory, SqlCall, SymbolFactory, TraversalVisitor,
+ DimensionSymbolFactory, MeasureSymbolFactory, SqlCall, SymbolFactory,
};
use crate::cube_bridge::base_tools::BaseTools;
use crate::cube_bridge::evaluator::CubeEvaluator;
-use crate::cube_bridge::join_hints::JoinHintItem;
use crate::cube_bridge::member_sql::MemberSql;
use crate::cube_bridge::security_context::SecurityContext;
use crate::planner::sql_call_builder::SqlCallBuilder;
@@ -18,6 +16,11 @@ use cubenativeutils::CubeError;
use std::collections::HashMap;
use std::rc::Rc;
+/// Compilation context for the planner. Resolves data-model
+/// declarations into `MemberSymbol`s, caches them by `SymbolPath`,
+/// and holds the JS-side interfaces (cube evaluator, base tools,
+/// security context) together with query-level metadata (timezone,
+/// alias overrides).
pub struct Compiler {
cube_evaluator: Rc,
base_tools: Rc,
@@ -49,6 +52,9 @@ impl Compiler {
}
}
+ /// Parses `name` as a `SymbolPath` and resolves it as the
+ /// appropriate member kind (dimension, measure, or segment).
+ /// Errors if the path points at a cube reference.
pub fn add_auto_resolved_member_evaluator(
&mut self,
name: String,
@@ -65,6 +71,8 @@ impl Compiler {
}
}
+ /// Resolves a measure by data-model path (`cube.measure` or a
+ /// cross-cube form). Cached.
pub fn add_measure_evaluator(
&mut self,
measure: String,
@@ -87,6 +95,9 @@ impl Compiler {
}
}
+ /// Resolves a dimension by data-model path. When the path turns
+ /// out to point at a segment, falls back to a parenthesized
+ /// member-expression wrapper.
pub fn add_dimension_evaluator(
&mut self,
dimension: String,
@@ -117,6 +128,9 @@ impl Compiler {
}
}
+ /// Resolves a segment by data-model path. Segments are
+ /// materialised as `MemberExpression` members so they plug into
+ /// the same machinery as other members.
pub fn add_segment_evaluator(&mut self, name: String) -> Result, CubeError> {
let path = SymbolPath::parse(self.cube_evaluator.clone(), &name)?;
self.add_segment_evaluator_by_path(path)
@@ -149,6 +163,8 @@ impl Compiler {
Ok(result)
}
+ /// Resolves a cube as an identifier — for `{CUBE}` / `{TABLE}`
+ /// placeholders. Cached by the normalised path.
pub fn add_cube_name_evaluator(
&mut self,
cube_name: String,
@@ -166,6 +182,8 @@ impl Compiler {
}
}
+ /// Resolves a cube as a table expression — for `{CUBE.sql()}`
+ /// placeholders. Cached by the normalised path.
pub fn add_cube_table_evaluator(
&mut self,
cube_name: String,
@@ -183,24 +201,20 @@ impl Compiler {
}
}
- pub fn join_hints(&self) -> Result, CubeError> {
- let mut collector = JoinHintsCollector::new();
- for member in self.members.values() {
- collector.apply(member, &())?;
- }
- Ok(collector.extract_result())
- }
-
pub fn timezone(&self) -> Tz {
self.timezone.clone()
}
+ /// Looks up an explicit alias override for a member's full name;
+ /// `None` when no override is set.
pub fn alias_for_member(&self, full_name: &str) -> Option {
self.member_to_alias
.as_ref()
.and_then(|m| m.get(full_name).cloned())
}
+ /// Compiles a JS `MemberSql` declaration into a `SqlCall` bound
+ /// to the given owning cube, via `SqlCallBuilder`.
pub fn compile_sql_call(
&mut self,
cube_name: &String,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/base_filter.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/base_filter.rs
index dbe2377747118..57b2991ebadd9 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/base_filter.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/base_filter.rs
@@ -5,13 +5,17 @@ use cubenativeutils::CubeError;
use itertools::Itertools;
use std::rc::Rc;
+/// Classifies a filter by the kind of member it targets. Drives where
+/// the filter is placed when the query is rendered (WHERE vs HAVING).
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FilterType {
Dimension,
Measure,
}
-// TODO: temporary compatibility proxy — collapse into TypedFilter and update FilterItem consumers
+/// Filter on a single member; thin wrapper over `TypedFilter`.
+// TODO: temporary compatibility proxy — collapse into TypedFilter
+// and update FilterItem consumers.
#[derive(Clone)]
pub struct BaseFilter {
typed_filter: TypedFilter,
@@ -61,10 +65,16 @@ impl BaseFilter {
Ok(Rc::new(Self { typed_filter }))
}
+ /// Member this filter applies to, with `TimeDimension` wrappers
+ /// peeled off to the underlying base dimension. Use
+ /// `raw_member_evaluator` to keep the wrapper.
pub fn member_evaluator(&self) -> Rc {
resolve_base_symbol(self.typed_filter.member_evaluator())
}
+ /// Member this filter applies to, exactly as it was given —
+ /// `TimeDimension` wrappers are kept. See `member_evaluator`
+ /// for the unwrapped form.
pub fn raw_member_evaluator(&self) -> Rc {
self.typed_filter.member_evaluator().clone()
}
@@ -86,6 +96,8 @@ impl BaseFilter {
Ok(Rc::new(Self { typed_filter }))
}
+ /// The filtered member as a time-dimension symbol when it is one;
+ /// `None` otherwise.
pub fn time_dimension_symbol(&self) -> Option> {
if self
.typed_filter
@@ -103,10 +115,15 @@ impl BaseFilter {
self.typed_filter.values()
}
+ /// Raw filter operator enum, matching the value declared in the
+ /// data model (`equals`, `in`, `inDateRange`, ...). See
+ /// `operation` for the decoded form ready for rendering.
pub fn filter_operator(&self) -> &FilterOperator {
self.typed_filter.operator()
}
+ /// Decoded, typed form of the filter operation, ready for
+ /// rendering. See `filter_operator` for the raw enum.
pub fn operation(&self) -> &super::typed_filter::FilterOp {
self.typed_filter.operation()
}
@@ -123,11 +140,15 @@ impl BaseFilter {
self.member_evaluator().full_name()
}
+ /// True when the filter compares its member to exactly one value
+ /// with the `Equal` operator.
pub fn is_single_value_equal(&self) -> bool {
self.typed_filter.values().len() == 1
&& *self.typed_filter.operator() == FilterOperator::Equal
}
+ /// Concrete allowed values when the operator is `In` or `Equal`,
+ /// otherwise `None`. `NULL`s in the value list are discarded.
pub fn get_value_restrictions(&self) -> Option> {
if *self.typed_filter.operator() == FilterOperator::In
|| *self.typed_filter.operator() == FilterOperator::Equal
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/base_segment.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/base_segment.rs
index ac2a889ad069d..629ad8aa35fb1 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/base_segment.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/base_segment.rs
@@ -4,6 +4,10 @@ use crate::planner::{
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// One `segments:` entry from the data model — a boolean expression
+/// attached to a cube under a name, materialised as a synthetic
+/// `MemberExpression` so it plugs into the same member machinery as
+/// dimensions and measures.
#[derive(Clone)]
pub struct BaseSegment {
full_name: String,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/compiler.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/compiler.rs
index e897a3fab1f08..e17abc1fbf922 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/compiler.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/compiler.rs
@@ -8,6 +8,11 @@ use cubenativeutils::CubeError;
use std::rc::Rc;
use std::str::FromStr;
+/// Compiles the data-model filter tree into three streams of
+/// `FilterItem`s — filters on dimensions, on time dimensions, and
+/// on measures. Time-dimension `date_range` attributes arrive
+/// separately via `add_time_dimension_item` and are normalised into
+/// `InDateRange` filters.
pub struct FilterCompiler<'a> {
evaluator_compiler: &'a mut Compiler,
query_tools: Rc,
@@ -38,6 +43,9 @@ impl<'a> FilterCompiler<'a> {
Ok(())
}
+ /// Lifts the optional `date_range` of a time-dimension request
+ /// into an explicit `InDateRange` filter on
+ /// `time_dimension_filters`.
pub fn add_time_dimension_item(&mut self, item: &Rc) -> Result<(), CubeError> {
if let Ok(td_item) = item.as_time_dimension() {
if let Some(date_range) = td_item.date_range_vec() {
@@ -54,6 +62,9 @@ impl<'a> FilterCompiler<'a> {
Ok(())
}
+ /// Consumes the compiler and returns the three collected
+ /// streams: `(dimension_filters, time_dimension_filters,
+ /// measure_filters)`.
pub fn extract_result(self) -> (Vec, Vec, Vec) {
(
self.dimension_filters,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/filter_operator.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/filter_operator.rs
index 5c6ca3a669d63..ca956c68655e3 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/filter_operator.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/filter_operator.rs
@@ -1,6 +1,10 @@
use cubenativeutils::CubeError;
use std::str::FromStr;
+/// Filter operator declared in the data model (`equals`, `in`,
+/// `gt`, `inDateRange`, ...). `RegularRollingWindowDateRange` and
+/// `ToDateRollingWindowDateRange` are synthetic — manufactured by
+/// the rolling-window planner, never coming from a query.
#[derive(Clone, PartialEq, Debug)]
pub enum FilterOperator {
Equal,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/comparison.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/comparison.rs
index 8b24cc443f251..76a7fdf40a32b 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/comparison.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/comparison.rs
@@ -6,6 +6,8 @@ pub enum ComparisonKind {
Lte,
}
+/// `Comparison` filter operation: compares the member against a
+/// single value with `>`, `>=`, `<`, or `<=`.
#[derive(Clone, Debug)]
pub struct ComparisonOp {
pub(crate) kind: ComparisonKind,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/date_range.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/date_range.rs
index 53db04f09d895..8f67d098f5aaf 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/date_range.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/date_range.rs
@@ -7,6 +7,8 @@ pub enum DateRangeKind {
NotInRange,
}
+/// `DateRange` filter operation: tests the member's date against an
+/// inclusive `[from, to]` range; `kind` chooses in-range vs not.
#[derive(Clone, Debug)]
pub struct DateRangeOp {
pub(crate) kind: DateRangeKind,
@@ -19,6 +21,8 @@ impl DateRangeOp {
Self { kind, from, to }
}
+ /// Formats `from` / `to` with the given fractional-seconds
+ /// precision, ready to be embedded into rendered SQL.
pub fn formatted_date_range(&self, precision: u32) -> Result<(String, String), CubeError> {
let from = QueryDateTimeHelper::format_from_date(&self.from, precision)?;
let to = QueryDateTimeHelper::format_to_date(&self.to, precision)?;
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/date_single.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/date_single.rs
index 8b162057f5282..207b9a2588f3b 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/date_single.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/date_single.rs
@@ -6,6 +6,8 @@ pub enum DateSingleKind {
AfterOrOn,
}
+/// `DateSingle` filter operation: compares the member's date to a
+/// single boundary (`Before`, `BeforeOrOn`, `After`, `AfterOrOn`).
#[derive(Clone, Debug)]
pub struct DateSingleOp {
pub(crate) kind: DateSingleKind,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/equality.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/equality.rs
index 04f1ae22fd663..93924b3843a75 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/equality.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/equality.rs
@@ -1,3 +1,5 @@
+/// `Equality` filter operation: tests the member for equality, or
+/// inequality when `negated`, against a single value.
#[derive(Clone, Debug)]
pub struct EqualityOp {
pub(crate) negated: bool,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/in_list.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/in_list.rs
index 50c6eb20d05d9..58cc91731fb2a 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/in_list.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/in_list.rs
@@ -1,3 +1,5 @@
+/// `InList` filter operation: tests whether the member belongs to,
+/// or — when `negated` — does not belong to, a list of values.
#[derive(Clone, Debug)]
pub struct InListOp {
pub(crate) negated: bool,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/like.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/like.rs
index 2a424fa6c7728..ad693df6562a1 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/like.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/like.rs
@@ -1,3 +1,5 @@
+/// `Like` filter operation: substring, prefix or suffix match
+/// (negated for the `not_*` variants).
#[derive(Clone, Debug)]
pub struct LikeOp {
pub(crate) negated: bool,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/measure_filter.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/measure_filter.rs
index 7c6a34d365811..48a825c93abe0 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/measure_filter.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/measure_filter.rs
@@ -1,3 +1,5 @@
+/// `MeasureFilter` filter operation — marker for the `measureFilter`
+/// data-model operator; carries no parameters of its own.
#[derive(Clone, Debug)]
pub struct MeasureFilterOp;
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/nullability.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/nullability.rs
index 45a7a3285ef71..dd13315cd9568 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/nullability.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/nullability.rs
@@ -1,3 +1,5 @@
+/// `Nullability` filter operation: `IS NULL`, or `IS NOT NULL` when
+/// `negated`.
#[derive(Clone, Debug)]
pub struct NullabilityOp {
pub(crate) negated: bool,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/rolling_window.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/rolling_window.rs
index b03568d9c32f7..8c57685d1ce01 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/rolling_window.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/rolling_window.rs
@@ -1,3 +1,6 @@
+/// `RegularRollingWindow` filter operation: trailing and leading
+/// interval bounds of a rolling window relative to each time-series
+/// point.
#[derive(Clone, Debug)]
pub struct RegularRollingWindowOp {
pub(crate) trailing: Option,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/to_date_rolling_window.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/to_date_rolling_window.rs
index 2541847f45e4c..90589d9b145fc 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/to_date_rolling_window.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/to_date_rolling_window.rs
@@ -1,5 +1,7 @@
use crate::planner::Granularity;
+/// `ToDateRollingWindow` filter operation: bounds a "since the start
+/// of " window — e.g. month-to-date, year-to-date.
#[derive(Clone)]
pub struct ToDateRollingWindowOp {
pub(crate) granularity: Granularity,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/tree.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/tree.rs
index ba5b64aa85830..6333acada6128 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/tree.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/tree.rs
@@ -3,12 +3,14 @@ use crate::planner::MemberSymbol;
use std::fmt;
use std::rc::Rc;
+/// Boolean operator combining the items of a `FilterGroup`.
#[derive(Clone, PartialEq)]
pub enum FilterGroupOperator {
Or,
And,
}
+/// Boolean combination of nested `FilterItem`s, joined by `operator`.
#[derive(Clone)]
pub struct FilterGroup {
pub operator: FilterGroupOperator,
@@ -27,6 +29,12 @@ impl FilterGroup {
}
}
+/// Node in a filter tree:
+///
+/// - `Group` — a nested boolean group (AND/OR).
+/// - `Item` — a leaf filter on a single member.
+/// - `Segment` — a segment-based filter (bool expression named in
+/// the data model).
#[derive(Clone, PartialEq)]
pub enum FilterItem {
Group(Rc),
@@ -34,6 +42,8 @@ pub enum FilterItem {
Segment(Rc),
}
+/// Top-level filter tree of a query — its `items` are implicitly
+/// AND-joined.
#[derive(Clone)]
pub struct Filter {
pub items: Vec,
@@ -49,6 +59,8 @@ impl fmt::Display for FilterGroupOperator {
}
impl Filter {
+ /// All members referenced anywhere in the filter tree, flattened
+ /// recursively through groups.
pub fn all_member_evaluators(&self) -> Vec> {
let mut result = Vec::new();
for item in self.items.iter() {
@@ -57,6 +69,9 @@ impl Filter {
result
}
+ /// Collapses the filter into a single `FilterItem`: `None` when
+ /// empty, the only item directly when one is present, or an
+ /// AND-`Group` wrapping the rest.
pub fn to_filter_item(&self) -> Option {
if self.items.is_empty() {
None
@@ -90,9 +105,9 @@ impl FilterItem {
}
}
- /// Extract all member symbols from this filter tree
- /// Returns None if filter tree is invalid (e.g., empty group)
- /// Returns Some(set) with all member symbols found in the tree
+ // Extracts all member symbols from this filter tree.
+ // Returns None when the tree is invalid (e.g. an empty group, or
+ // anything below a Segment item), Some(members) otherwise.
fn extract_filter_members(&self) -> Option>> {
match self {
FilterItem::Group(group) => {
@@ -118,11 +133,10 @@ impl FilterItem {
}
}
- /// Find subtree of filters that only contains filters for the specified members
- /// Returns None if no matching filters found
- /// Returns Some(FilterItem) with the subtree containing only filters for target members
- ///
- /// This only processes AND groups - OR groups are not supported and will return None
+ /// Returns the largest subtree that only references the given
+ /// `target_members`, or `None` if no such subtree exists. Only
+ /// AND groups are traversed for partial matching — OR groups
+ /// either match as a whole or are dropped.
pub fn find_subtree_for_members(&self, target_members: &[&String]) -> Option {
match self {
FilterItem::Group(group) => {
@@ -190,11 +204,14 @@ impl FilterItem {
}
}
- /// Find value restrictions for a given symbol across filter tree
- /// Returns:
- /// - None: no restrictions found for this symbol
- /// - Some(vec![]): restrictions exist but result in empty set (contradiction)
- /// - Some(values): list of allowed values for this symbol
+ /// Collects the set of allowed values for `symbol` from the
+ /// filter tree.
+ ///
+ /// - `None` — no restriction is placed on `symbol`.
+ /// - `Some(vec![])` — restrictions exist but contradict each
+ /// other (empty set).
+ /// - `Some(values)` — explicit list of values the symbol may
+ /// take. AND groups intersect, OR groups union.
pub fn find_value_restriction(&self, symbol: &Rc) -> Option> {
match self {
FilterItem::Item(item) => {
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/typed_filter.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/typed_filter.rs
index a015a6ad2fb6f..cad84a60fd0e9 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/typed_filter.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/typed_filter.rs
@@ -26,6 +26,10 @@ pub fn resolve_base_symbol(symbol: &Rc) -> Rc {
}
}
+/// Typed, ready-to-render filter operation. Decoded once at filter
+/// construction from a `FilterOperator` plus its value list, each
+/// variant carrying exactly the parameters its rendering needs
+/// (compared value, date bounds, granularity, etc.).
#[derive(Clone, Debug)]
pub enum FilterOp {
Comparison(ComparisonOp),
@@ -40,6 +44,10 @@ pub enum FilterOp {
ToDateRollingWindow(ToDateRollingWindowOp),
}
+/// Filter bound to a member and decoded into a typed `FilterOp`.
+/// Carries both the raw operator + value list (the form that came
+/// from the data model) and the decoded form, so renderers can pick
+/// whichever view they need.
#[derive(Clone)]
pub struct TypedFilter {
query_tools: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/join_hints.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/join_hints.rs
index f590b21e32274..f7efa2650b8d5 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/join_hints.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/join_hints.rs
@@ -1,5 +1,9 @@
use crate::cube_bridge::join_hints::JoinHintItem;
+/// Ordered list of cube-join hints. Adjacent redundant entries are
+/// silently dropped on `push` / `extend` — a `Single` is skipped when
+/// it duplicates either the previous `Single` or the tail of the
+/// previous `Vector`.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct JoinHints {
items: Vec,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/multi_fact_join_groups.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/multi_fact_join_groups.rs
index b00908fa23313..51966b1d15bf7 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/multi_fact_join_groups.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/multi_fact_join_groups.rs
@@ -10,12 +10,17 @@ use itertools::Itertools;
use std::collections::HashMap;
use std::rc::Rc;
+/// A single measure paired with the complete set of join hints it
+/// needs — base query hints plus the measure's own incremental hints.
#[derive(Clone, Debug)]
pub struct MeasureJoinHints {
pub measure: Rc,
pub hints: JoinHints,
}
+/// Accumulates the join-hint context of a query (initial
+/// `query_join_hints`, dimensions, filters) and produces
+/// `MeasuresJoinHints` for a given set of measures.
pub struct MeasuresJoinHintsBuilder {
initial_hints: JoinHints,
dimensions: Vec>,
@@ -52,6 +57,13 @@ impl MeasuresJoinHintsBuilder {
}
}
+/// Join-hint context of a query split into:
+///
+/// - `base_hints` — hints shared across all measures (query-level
+/// hints plus those collected from dimensions and filters).
+/// - `measure_hints` — per-measure incremental hints, one entry per
+/// non-multi-stage measure. Multi-stage measures plan their joins
+/// separately and are skipped here.
#[derive(Clone, Debug)]
pub struct MeasuresJoinHints {
base_hints: JoinHints,
@@ -59,6 +71,7 @@ pub struct MeasuresJoinHints {
}
impl MeasuresJoinHints {
+ /// Start a builder seeded with the query-level join hints.
pub fn builder(query_join_hints: &JoinHints) -> MeasuresJoinHintsBuilder {
MeasuresJoinHintsBuilder {
initial_hints: query_join_hints.clone(),
@@ -67,6 +80,8 @@ impl MeasuresJoinHints {
}
}
+ /// Reuse the existing `base_hints` to produce a new
+ /// `MeasuresJoinHints` over a different measure subset.
pub fn for_measures(&self, measures: &[Rc]) -> Result {
Self::from_base_hints(self.base_hints.clone(), measures)
}
@@ -108,6 +123,9 @@ impl MeasuresJoinHints {
&self.measure_hints
}
+ /// Combined hints for a specific measure (base plus the measure's
+ /// own), or `None` when the measure has no entry — typically
+ /// because it is multi-stage.
pub fn hints_for_measure(&self, measure: &MemberSymbol) -> Option {
self.measure_hints
.iter()
@@ -118,6 +136,14 @@ impl MeasuresJoinHints {
// --- MultiFactJoinGroups: builds actual join trees ---
+/// Resolves a query's `MeasuresJoinHints` into concrete join trees
+/// and groups measures by the `JoinKey` of the cube graph they share.
+/// More than one group means the query is **multi-fact**: it touches
+/// measures from cubes that cannot be combined under a single join.
+///
+/// Per-cube join paths from the root of each group are precomputed so
+/// `resolve_join_path_for_dimension` / `resolve_join_path_for_measure`
+/// are cheap lookups at render time.
#[derive(Clone)]
pub struct MultiFactJoinGroups {
query_tools: Rc,
@@ -130,6 +156,8 @@ pub struct MultiFactJoinGroups {
}
impl MultiFactJoinGroups {
+ /// Builds the join trees from `measures_join_hints` and groups
+ /// measures by the resulting `JoinKey`.
pub fn try_new(
query_tools: Rc,
measures_join_hints: MeasuresJoinHints,
@@ -145,6 +173,8 @@ impl MultiFactJoinGroups {
})
}
+ /// Rebuilds the groups for a different measure subset, reusing
+ /// the shared `base_hints`.
pub fn for_measures(&self, measures: &[Rc]) -> Result {
let new_hints = self.measures_join_hints.for_measures(measures)?;
Self::try_new(self.query_tools.clone(), new_hints)
@@ -194,6 +224,7 @@ impl MultiFactJoinGroups {
&self.measures_join_hints
}
+ /// True when the query's measures span more than one join tree.
pub fn is_multi_fact(&self) -> bool {
self.groups.len() > 1
}
@@ -272,6 +303,9 @@ impl MultiFactJoinGroups {
Ok(paths)
}
+ /// The only join when the query has exactly one group; errors if
+ /// the query is multi-fact, and `Ok(None)` if it has no groups
+ /// at all.
pub fn single_join(&self) -> Result>, CubeError> {
if self.groups.is_empty() {
return Ok(None);
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/common_utils.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/common_utils.rs
index 9b48178bdba24..76efcc10beed9 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/common_utils.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/common_utils.rs
@@ -4,6 +4,8 @@ use crate::planner::MemberSymbol;
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// Small helpers shared between planners — cube resolution and the
+/// list of primary-key dimensions for a given cube.
pub struct CommonUtils {
query_tools: Rc,
}
@@ -13,6 +15,7 @@ impl CommonUtils {
Self { query_tools }
}
+ /// Resolves the planner-level `BaseCube` for the given cube path.
pub fn cube_from_path(&self, cube_path: String) -> Result, CubeError> {
let evaluator_compiler_cell = self.query_tools.evaluator_compiler().clone();
let mut evaluator_compiler = evaluator_compiler_cell.borrow_mut();
@@ -22,6 +25,8 @@ impl CommonUtils {
BaseCube::try_new(cube_path.to_string(), self.query_tools.clone(), evaluator)
}
+ /// Primary-key dimensions of `cube_name` as planner
+ /// `MemberSymbol`s.
pub fn primary_keys_dimensions(
&self,
cube_name: &String,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/dimension_subquery_planner.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/dimension_subquery_planner.rs
index 64dda0181cd8a..c91fbd7cf6bf1 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/dimension_subquery_planner.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/dimension_subquery_planner.rs
@@ -11,6 +11,10 @@ use std::cell::{Ref, RefCell};
use std::collections::HashMap;
use std::rc::Rc;
+/// Plans `DimensionSubQuery` nodes for `sub_query: true` dimensions.
+/// Each subquery dimension becomes its own `Query` over the owning
+/// cube's primary keys plus the dimension's measure expression, then
+/// gets joined back into the host query on those keys.
pub struct DimensionSubqueryPlanner {
utils: CommonUtils,
query_tools: Rc,
@@ -20,6 +24,8 @@ pub struct DimensionSubqueryPlanner {
}
impl DimensionSubqueryPlanner {
+ /// Planner with no sub-query dimensions — used when the host
+ /// query has none.
pub fn empty(query_tools: Rc, query_properties: Rc) -> Self {
Self {
sub_query_dims: HashMap::new(),
@@ -29,6 +35,8 @@ impl DimensionSubqueryPlanner {
dimensions_refs: RefCell::new(HashMap::new()),
}
}
+ /// Builds a planner over the given sub-query dimensions, indexed
+ /// by owning cube.
pub fn try_new(
dimensions: &Vec>,
query_tools: Rc,
@@ -52,6 +60,7 @@ impl DimensionSubqueryPlanner {
})
}
+ /// Plans one `DimensionSubQuery` per dimension in the input list.
pub fn plan_queries(
&self,
dimensions: &Vec>,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/full_key_query_aggregate_planner.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/full_key_query_aggregate_planner.rs
index 6fbf06027f756..66af9afd708da 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/full_key_query_aggregate_planner.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/full_key_query_aggregate_planner.rs
@@ -3,6 +3,9 @@ use crate::planner::QueryProperties;
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// Final assembly step for non-simple queries: takes the collected
+/// multi-stage / multiplied subquery refs and members and wraps
+/// them in a `Query` whose source is a `FullKeyAggregate`.
pub struct FullKeyAggregateQueryPlanner {
query_properties: Rc,
}
@@ -12,6 +15,9 @@ impl FullKeyAggregateQueryPlanner {
Self { query_properties }
}
+ /// Builds the `FullKeyAggregate` source from the collected
+ /// multi-stage subquery refs. Always renders as FULL OUTER JOIN
+ /// + COALESCE.
pub fn plan_logical_source(
&self,
multi_stage_subqueries: Vec>,
@@ -29,6 +35,8 @@ impl FullKeyAggregateQueryPlanner {
))
}
+ /// Wraps `plan_logical_source` in a `Query` with the request's
+ /// filters, modifiers and multi-stage members.
pub fn plan_logical_plan(
&self,
multi_stage_subqueries: Vec>,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/join_planner.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/join_planner.rs
index 7d7064bbca5a4..0f2fc936500fc 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/join_planner.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/join_planner.rs
@@ -9,6 +9,10 @@ use crate::planner::SqlCall;
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// Join item with its members resolved to `MemberSymbol`s on both
+/// sides — produced from a `JoinDefinition` by `JoinPlanner` so the
+/// rest of the planner can reason about the actual columns involved
+/// without re-parsing the JS-side ON clause.
#[derive(Clone, Debug)]
pub struct ResolvedJoinItem {
pub original_from: String,
@@ -19,6 +23,8 @@ pub struct ResolvedJoinItem {
}
impl ResolvedJoinItem {
+ /// Equality on the cube pair and the resolved member sets — used
+ /// to detect duplicates without comparing the compiled `on_sql`.
pub fn is_same_as(&self, other: &Self) -> bool {
self.original_from == other.original_from
&& self.original_to == other.original_to
@@ -27,6 +33,9 @@ impl ResolvedJoinItem {
}
}
+/// Builds `LogicalJoin` trees from `JoinDefinition`s (or join
+/// hints), compiles each item's ON SQL into a `SqlCall`, and exposes
+/// helpers for resolving the members each ON clause references.
pub struct JoinPlanner {
utils: CommonUtils,
query_tools: Rc,
@@ -40,6 +49,8 @@ impl JoinPlanner {
}
}
+ /// Builds a `LogicalJoin` from join hints, asking the join graph
+ /// to materialise the matching `JoinDefinition`.
pub fn make_join_logical_plan_with_join_hints(
&self,
join_hints: JoinHints,
@@ -52,10 +63,15 @@ impl JoinPlanner {
self.make_join_logical_plan(join, dimension_subqueries)
}
+ /// Empty `LogicalJoin` — used when the query needs no joins
+ /// (e.g. it touches a single cube and pulls nothing extra).
pub fn make_empty_join_logical_plan(&self) -> Rc {
Rc::new(LogicalJoin::builder().build())
}
+ /// Builds a `LogicalJoin` from an already-resolved
+ /// `JoinDefinition`, compiling each item's ON SQL and attaching
+ /// the given sub-query dimensions.
pub fn make_join_logical_plan(
&self,
join: Rc,
@@ -83,6 +99,8 @@ impl JoinPlanner {
))
}
+ /// Compiles the ON SQL of a join item into a `SqlCall` rooted at
+ /// the `from` cube.
pub fn compile_join_condition(
&self,
join_item: Rc,
@@ -94,6 +112,8 @@ impl JoinPlanner {
.compile_sql_call(&join_item.static_data().original_from, definition.sql()?)
}
+ /// Materialises the join from `join_hints` and resolves the
+ /// members each ON clause references.
pub fn resolve_join_members_by_hints(
&self,
join_hints: &JoinHints,
@@ -104,6 +124,8 @@ impl JoinPlanner {
.build_join(join_hints.items().to_vec())?;
self.resolve_join_members(join)
}
+ /// Resolves the members each ON clause references for an
+ /// already-built `JoinDefinition`.
pub fn resolve_join_members(
&self,
join: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/cte_state.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/cte_state.rs
index aabac32044f2c..73c35ce5442a1 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/cte_state.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/cte_state.rs
@@ -1,6 +1,11 @@
use crate::logical_plan::{LogicalMultiStageMember, MultiStageSubqueryRef};
use std::rc::Rc;
+/// Accumulator the multi-stage / multiplied planners write CTEs
+/// into during planning: a monotonic counter for generated CTE
+/// names (`cte_0`, `cte_1`, ...), the list of
+/// `LogicalMultiStageMember`s, and the subquery refs that the
+/// outer `FullKeyAggregate` will join over.
pub struct CteState {
counter: usize,
members: Vec>,
@@ -16,6 +21,7 @@ impl CteState {
}
}
+ /// Generates the next unique CTE name (`cte_0`, `cte_1`, ...).
pub fn next_cte_name(&mut self) -> String {
let name = format!("cte_{}", self.counter);
self.counter += 1;
@@ -30,6 +36,8 @@ impl CteState {
self.subquery_refs.push(subquery_ref);
}
+ /// Consumes the state, returning the accumulated CTE members
+ /// and subquery refs.
pub fn into_results(
self,
) -> (
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member.rs
index 082146a9a90c6..e1b2f596c8851 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member.rs
@@ -1,12 +1,18 @@
use crate::planner::{MeasureTimeShifts, MemberSymbol};
use std::rc::Rc;
+/// Description of the time-series CTE driving a rolling-window
+/// computation: the time dimension and, optionally, the alias of a
+/// sibling CTE that resolves its date range at query time.
#[derive(Clone)]
pub struct TimeSeriesDescription {
pub time_dimension: Rc,
pub date_range_cte: Option,
}
+/// Kind of leaf CTE in a multi-stage chain: a base measure query,
+/// a time-series axis, or a query that just resolves the date range
+/// of a time dimension.
#[derive(Clone)]
pub enum MultiStageLeafMemberType {
Measure,
@@ -14,6 +20,8 @@ pub enum MultiStageLeafMemberType {
TimeSeriesGetRange(Rc),
}
+/// Bounds of a regular rolling window: trailing / leading interval
+/// strings and a time-series offset.
#[derive(Clone)]
pub struct RegularRollingWindow {
pub trailing: Option,
@@ -21,11 +29,15 @@ pub struct RegularRollingWindow {
pub offset: String,
}
+/// To-date rolling window — accumulates since the start of
+/// `granularity` (month-to-date, year-to-date, …).
#[derive(Clone)]
pub struct ToDateRollingWindow {
pub granularity: String,
}
+/// Flavour of rolling-window computation: regular trailing/leading
+/// window, to-date window, or a running-total accumulation.
#[derive(Clone)]
pub enum RollingWindowType {
Regular(RegularRollingWindow),
@@ -33,6 +45,9 @@ pub enum RollingWindowType {
RunningTotal,
}
+/// Planner-side description of a rolling window: the time
+/// dimension used for windowing (and its lower-granularity base
+/// version produced in the leaf CTE) plus the chosen window type.
#[derive(Clone)]
pub struct RollingWindowDescription {
pub time_dimension: Rc,
@@ -84,6 +99,9 @@ impl RollingWindowDescription {
}
}
+/// Semantic shape of a non-leaf multi-stage CTE: a rank window,
+/// an aggregate (possibly window-rendered), a non-aggregating
+/// calculation, a dimension calculation, or a rolling window.
#[derive(Clone)]
pub enum MultiStageInodeMemberType {
Rank,
@@ -93,6 +111,11 @@ pub enum MultiStageInodeMemberType {
RollingWindow(RollingWindowDescription),
}
+/// Non-leaf node in a multi-stage tree. Bundles the semantic
+/// `inode_type` (Rank / Aggregate / Calculate / Dimension /
+/// RollingWindow) with the partition-shaping flags driven by the
+/// measure's data-model directives: `reduce_by`, `add_group_by`,
+/// `group_by`, `time_shift`.
#[derive(Clone)]
pub struct MultiStageInodeMember {
inode_type: MultiStageInodeMemberType,
@@ -154,6 +177,9 @@ impl MultiStageInodeMember {
}
}
+/// Position of a CTE in the multi-stage tree: either an inner node
+/// (depends on other CTEs) or a leaf (queries the underlying data
+/// directly).
#[derive(Clone)]
pub enum MultiStageMemberType {
Inode(MultiStageInodeMember),
@@ -169,6 +195,9 @@ impl MultiStageMemberType {
}
}
+/// One node in a multi-stage tree: its position (`member_type`),
+/// the member symbol it renders, and a few rendering flags
+/// (ungrouped, has-aggregates-on-top, is-without-member-leaf).
pub struct MultiStageMember {
member_type: MultiStageMemberType,
member_symbol: Rc,
@@ -193,6 +222,10 @@ impl MultiStageMember {
})
}
+ /// Builds a leaf node whose base CTE selects only dimensions —
+ /// the member itself is not computed by the leaf (e.g. for a
+ /// `Rank` measure where the value comes purely from a window
+ /// function applied on top).
pub fn new_without_member_leaf(
member_type: MultiStageMemberType,
evaluation_node: Rc,
@@ -216,6 +249,9 @@ impl MultiStageMember {
&self.member_symbol
}
+ /// True when this node is a leaf whose CTE selects dimensions
+ /// only, without computing the member's value (see
+ /// `new_without_member_leaf`).
pub fn is_without_member_leaf(&self) -> bool {
self.is_without_member_leaf
}
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member_query_planner.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member_query_planner.rs
index b19bad335bcc8..017ada18774c4 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member_query_planner.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member_query_planner.rs
@@ -14,6 +14,11 @@ use itertools::Itertools;
use std::rc::Rc;
use std::vec;
+/// Renders one `MultiStageQueryDescription` into a
+/// `LogicalMultiStageMember`. The shape of the output is dispatched
+/// from the description's `MultiStageMemberType`: rolling-window /
+/// dimension / measure inode, or a leaf (base measure /
+/// time-series / time-series-get-range).
pub struct MultiStageMemberQueryPlanner {
query_tools: Rc,
query_properties: Rc,
@@ -33,6 +38,9 @@ impl MultiStageMemberQueryPlanner {
}
}
+ /// Builds the `LogicalMultiStageMember` for this description,
+ /// dispatching on `MultiStageMemberType` to the appropriate
+ /// `plan_*` builder.
pub fn plan_logical_query(&self) -> Result, CubeError> {
match self.description.member().member_type() {
MultiStageMemberType::Inode(member) => match member.inode_type() {
@@ -54,6 +62,10 @@ impl MultiStageMemberQueryPlanner {
}
}
+ /// Builds the leaf `GetDateRange` CTE used when a rolling-window
+ /// time dimension has no explicit date range — runs a
+ /// `SimpleQueryPlanner` to compute the actual bounds at query
+ /// time.
fn plan_time_series_get_range_query(
&self,
time_dimension: Rc,
@@ -85,6 +97,10 @@ impl MultiStageMemberQueryPlanner {
Ok(Rc::new(member))
}
+ /// Builds the leaf `TimeSeries` CTE — the date axis a rolling
+ /// window walks over. References a sibling `GetDateRange` CTE
+ /// for its bounds when the time dimension has no explicit
+ /// `date_range`.
fn plan_time_series_query(
&self,
time_series_description: Rc,
@@ -101,6 +117,10 @@ impl MultiStageMemberQueryPlanner {
}))
}
+ /// Builds the rolling-window CTE that combines a time-series
+ /// input with a measure input, dispatching on
+ /// `RollingWindowDescription` into the regular / to-date /
+ /// running-total variant.
fn plan_rolling_window_query(
&self,
rolling_window_desc: &RollingWindowDescription,
@@ -174,6 +194,12 @@ impl MultiStageMemberQueryPlanner {
}))
}
+ /// Builds a measure-calculation CTE (Rank / Aggregate /
+ /// Calculate). Picks the partition-by from the inode's
+ /// `reduce_by` / `group_by` settings, chooses a window-function
+ /// flavour (Rank, Window, or None) when the partition is
+ /// narrower than the full dimension set, and wires the input
+ /// CTEs into a `FullKeyAggregate` source.
fn plan_for_cte_query(
&self,
multi_stage_member: &MultiStageInodeMember,
@@ -255,6 +281,11 @@ impl MultiStageMemberQueryPlanner {
Ok(Rc::new(result))
}
+ /// Builds a dimension-calculation CTE for a multi-stage
+ /// dimension. Includes the dimension itself plus every
+ /// non-multi-stage dimension reachable from the subtree, so the
+ /// resulting CTE can be joined back into the host query on
+ /// those dimensions.
fn plan_for_cte_dimension_query(
&self,
_multi_stage_member: &MultiStageInodeMember,
@@ -339,6 +370,12 @@ impl MultiStageMemberQueryPlanner {
Ok(Rc::new(result))
}
+ /// Builds the leaf CTE for a base measure — runs a fresh
+ /// `QueryPlanner` on the description's state with
+ /// `allow_multi_stage = false`, then wraps the result in a
+ /// `MultiStageLeafMeasure`. Respects the `without-member-leaf`
+ /// shape for cases like `Rank` where the leaf selects only the
+ /// dimension grid.
fn plan_for_leaf_cte_query(&self) -> Result, CubeError> {
let member_node = self.description.member_node();
let mut dimensions = self.description.state().dimensions().clone();
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/multi_stage_query_planner.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/multi_stage_query_planner.rs
index 9ebb0aab879a7..2a74aeb8e0506 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/multi_stage_query_planner.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/multi_stage_query_planner.rs
@@ -26,6 +26,13 @@ use itertools::Itertools;
use std::collections::HashSet;
use std::rc::Rc;
+/// Plans the multi-stage CTE tree of a query. For every multi-stage
+/// member it encounters in `all_used_symbols`, it recursively
+/// produces `MultiStageQueryDescription`s for the member and its
+/// dependencies, then asks `MultiStageMemberQueryPlanner` to render
+/// each into a `LogicalMultiStageMember`. CTEs are deduplicated by
+/// `(member, state)` so the same multi-stage subquery isn't
+/// emitted twice.
pub struct MultiStageQueryPlanner {
query_tools: Rc,
query_properties: Rc,
@@ -39,6 +46,9 @@ impl MultiStageQueryPlanner {
}
}
+ /// Populates `cte_state` with multi-stage CTEs (and their
+ /// subquery refs) for every multi-stage member used by the
+ /// query. No-op when the query has none.
pub fn plan_queries(&self, cte_state: &mut CteState) -> Result<(), CubeError> {
let multi_stage_members = self
.query_properties
@@ -105,6 +115,12 @@ impl MultiStageQueryPlanner {
Ok(())
}
+ /// Classifies `base_member` into a `MultiStageInodeMember` — picks
+ /// the inode kind (Rank / Aggregate / Calculate for a measure,
+ /// Dimension for a dimension) and pulls the partition-shaping
+ /// flags (`reduce_by`, `add_group_by`, `group_by`, `time_shift`)
+ /// out of the data-model definition. Returns the inode together
+ /// with the leaf's `is_ungrupped` flag.
fn create_multi_stage_inode_member(
&self,
base_member: Rc,
@@ -159,6 +175,10 @@ impl MultiStageQueryPlanner {
Ok(inode)
}
+ /// Builds child descriptions for `member`'s inode. Switches to
+ /// `try_make_childs_for_case_switch` when the member's body is a
+ /// CASE-SWITCH expression; otherwise falls through to
+ /// `default_make_childs`.
fn make_childs(
&self,
member: Rc,
@@ -190,6 +210,8 @@ impl MultiStageQueryPlanner {
)
}
+ /// True if `member` is a dimension that has multi-stage members
+ /// somewhere in its dependency tree.
fn is_multi_stage_dimension(member: &Rc) -> Result {
if member.is_dimension() {
has_multi_stage_members(member, false)
@@ -198,6 +220,12 @@ impl MultiStageQueryPlanner {
}
}
+ /// Default child-generation path: for each measure or
+ /// multi-stage-dimension dependency, recurses into
+ /// `make_queries_descriptions` and adds the result as an input
+ /// CTE. If the member has no such deps (e.g. a `Rank` measure
+ /// that only needs the dimension grid), produces a single
+ /// "without-member" leaf instead.
fn default_make_childs(
&self,
member: Rc,
@@ -245,6 +273,13 @@ impl MultiStageQueryPlanner {
Ok(())
}
+ /// Plans CASE-SWITCH dependencies: collects, per dependency, the
+ /// union of switch values it covers and renders each dependency
+ /// under a state with an equality filter on the switch member
+ /// restricted to those values. An open `ELSE` branch makes the
+ /// dependency unrestricted. Returns `false` when the switch is
+ /// not a member reference, in which case the caller falls back
+ /// to `default_make_childs`.
fn try_make_childs_for_case_switch(
&self,
case: &CaseSwitchDefinition,
@@ -322,6 +357,15 @@ impl MultiStageQueryPlanner {
Ok(true)
}
+ /// Core recursive step. Given a `member` and the current
+ /// `state`, resolves the reference chain, applies static filters
+ /// from the dimensions filters, deduplicates against
+ /// already-built descriptions, tries a rolling-window path
+ /// (`try_plan_rolling_window`), and otherwise returns either a
+ /// leaf `Measure` or an inode description whose children come
+ /// from `make_childs`. Adjusts the state for inodes with any
+ /// `add_group_by`, time-shift or per-member filter changes the
+ /// inode demands.
fn make_queries_descriptions(
&self,
member: Rc,
@@ -431,6 +475,10 @@ impl MultiStageQueryPlanner {
Ok(description)
}
+ /// If `member` is a cumulative measure, plans the time-series
+ /// and rolling-window CTEs and returns the rolling-window
+ /// description. Returns `None` for other measures and for
+ /// non-measure members.
pub fn try_plan_rolling_window(
&self,
member: Rc,
@@ -470,7 +518,7 @@ impl MultiStageQueryPlanner {
}
}
- let ungrouped = measure.is_rolling_window() && !measure.is_addictive();
+ let ungrouped = measure.is_rolling_window() && !measure.is_additive();
let mut time_dimensions = self
.query_properties
@@ -604,6 +652,9 @@ impl MultiStageQueryPlanner {
}
}
+ /// Adds (or reuses) the `time_series_get_range` leaf CTE — used
+ /// by `add_time_series` when the requested time dimension has no
+ /// explicit date range and the planner needs to compute one.
fn add_time_series_get_range_query(
&self,
time_dimension: Rc,
@@ -635,6 +686,10 @@ impl MultiStageQueryPlanner {
Ok(description)
}
+ /// Adds (or reuses) the `time_series` leaf CTE that drives a
+ /// rolling window. When the time dimension has no `date_range`,
+ /// also arranges for a sibling `time_series_get_range` CTE to
+ /// feed it.
fn add_time_series(
&self,
time_dimension: Rc,
@@ -681,6 +736,10 @@ impl MultiStageQueryPlanner {
Ok(description)
}
+ /// Adds the leaf CTE that produces the base values for a
+ /// rolling window — selects the requested dimensions plus the
+ /// unrolled measure, marked `has_aggregates_on_top` so callers
+ /// know an outer rolling computation will consume it.
fn add_rolling_window_base(
&self,
member: Rc,
@@ -705,6 +764,9 @@ impl MultiStageQueryPlanner {
Ok(description)
}
+ /// Returns the granularity of a `to_date` rolling window. Errors
+ /// if the window is declared as `to_date` without a granularity,
+ /// and returns `None` for window kinds that don't carry one.
fn get_to_date_rolling_granularity(
&self,
rolling_window: &RollingWindow,
@@ -750,6 +812,12 @@ impl MultiStageQueryPlanner {
Ok(Rc::new(new_state))
}
+ /// Builds the state for a rolling-window base CTE: reduces the
+ /// time dimension to the minimum granularity required by the
+ /// window, drops other dimensions that resolve to time
+ /// dimensions, and rewrites the time-dimension date-range
+ /// filter to either a `to_date` bound or a regular trailing /
+ /// leading bound.
fn make_rolling_base_state(
&self,
time_dimension: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/query_description.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/query_description.rs
index c264ae10da684..c412b93125648 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/query_description.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/query_description.rs
@@ -1,10 +1,15 @@
use super::MultiStageMember;
use crate::logical_plan::LogicalSchema;
+use crate::planner::collectors::has_multi_stage_members;
use crate::planner::{MemberSymbol, QueryProperties};
use cubenativeutils::CubeError;
use itertools::Itertools;
use std::rc::Rc;
+/// One CTE in the multi-stage tree as the planner sees it: the
+/// `member` rendered, the `state` (`QueryProperties` snapshot for
+/// this CTE's scope), the input CTEs it depends on, and the alias
+/// it will be referenced by.
pub struct MultiStageQueryDescription {
member: Rc,
state: Rc,
@@ -67,6 +72,10 @@ impl MultiStageQueryDescription {
self.input.is_empty()
}
+ /// Walks the description subtree and returns
+ /// `(dimensions, time_dimensions)` whose chain-resolved members
+ /// have no multi-stage members of their own. Duplicates are
+ /// removed by full name.
pub fn collect_all_non_multi_stage_dimension(
&self,
) -> Result<(Vec>, Vec>), CubeError> {
@@ -76,14 +85,9 @@ impl MultiStageQueryDescription {
let dimensions = dimensions
.into_iter()
.unique_by(|d| d.full_name())
- .filter_map(|d| match d.is_basic_dimension() {
- Ok(res) => {
- if res {
- None
- } else {
- Some(Ok(d))
- }
- }
+ .filter_map(|d| match has_multi_stage_members(&d, true) {
+ Ok(true) => None,
+ Ok(false) => Some(Ok(d)),
Err(e) => Some(Err(e)),
})
.collect::, _>>()?;
@@ -91,14 +95,9 @@ impl MultiStageQueryDescription {
let time_dimensions = time_dimensions
.into_iter()
.unique_by(|d| d.full_name())
- .filter_map(|d| match d.is_basic_dimension() {
- Ok(res) => {
- if res {
- None
- } else {
- Some(Ok(d))
- }
- }
+ .filter_map(|d| match has_multi_stage_members(&d, true) {
+ Ok(true) => None,
+ Ok(false) => Some(Ok(d)),
Err(e) => Some(Err(e)),
})
.collect::, _>>()?;
@@ -117,6 +116,10 @@ impl MultiStageQueryDescription {
}
}
+ /// True if this description renders `member_node` under an
+ /// equivalent state — used to deduplicate CTEs when the same
+ /// member is reached through different paths in the dependency
+ /// graph.
pub fn is_match_member_and_state(
&self,
member_node: &Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/time_shift_state.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/time_shift_state.rs
index 921546ac8cf17..e603f0e21860d 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/time_shift_state.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/time_shift_state.rs
@@ -3,6 +3,9 @@ use crate::planner::DimensionTimeShift;
use cubenativeutils::CubeError;
use std::collections::HashMap;
+/// Per-dimension time-shift accumulator used during multi-stage
+/// planning. Keyed by dimension full name; aggregates the shifts
+/// applied to each dimension across nested multi-stage scopes.
#[derive(Clone, Default, Debug, PartialEq)]
pub struct TimeShiftState {
pub dimensions_shifts: HashMap,
@@ -13,6 +16,11 @@ impl TimeShiftState {
self.dimensions_shifts.is_empty()
}
+ /// Splits the accumulated shifts into two maps: regular
+ /// `DimensionTimeShift`s applied at render time, and
+ /// `CalendarDimensionTimeShift`s that come from a calendar
+ /// cube's own time-shift declarations. A named shift that does
+ /// not match a calendar declaration is an error.
pub fn extract_time_shifts(
&self,
) -> Result<
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multiplied_measures_query_planner.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multiplied_measures_query_planner.rs
index 79429a52095df..115f89f1c70bb 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multiplied_measures_query_planner.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multiplied_measures_query_planner.rs
@@ -13,6 +13,11 @@ use cubenativeutils::CubeError;
use itertools::Itertools;
use std::rc::Rc;
+/// Plans the per-measure CTEs that feed `FullKeyAggregate` in
+/// non-simple queries: regular measures become `MultiStageLeafMeasure`
+/// CTEs (one per multi-fact group), and multiplied measures become
+/// `AggregateMultipliedSubquery` CTEs (one per owning cube). Both
+/// kinds are registered into the shared `CteState`.
pub struct MultipliedMeasuresQueryPlanner {
query_tools: Rc,
query_properties: Rc,
@@ -22,6 +27,9 @@ pub struct MultipliedMeasuresQueryPlanner {
}
impl MultipliedMeasuresQueryPlanner {
+ /// Constructs the planner and caches
+ /// `full_key_aggregate_measures` for later use in
+ /// `plan_queries`.
pub fn try_new(
query_tools: Rc,
query_properties: Rc,
@@ -36,6 +44,10 @@ impl MultipliedMeasuresQueryPlanner {
})
}
+ /// Registers per-measure CTEs into `cte_state`: regular measures
+ /// become leaf-measure CTEs grouped by multi-fact join, multiplied
+ /// measures become `AggregateMultipliedSubquery` CTEs grouped by
+ /// owning cube. Errors if called on a simple query.
pub fn plan_queries(&self, cte_state: &mut CteState) -> Result<(), CubeError> {
if self.query_properties.is_simple_query()? {
return Err(CubeError::internal(format!(
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/order_planner.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/order_planner.rs
index 97acff93c32e3..fb3beb5ab8b58 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/order_planner.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/order_planner.rs
@@ -3,6 +3,9 @@ use crate::planner::MemberSymbol;
use crate::planner::{OrderByItem, QueryProperties};
use std::rc::Rc;
+/// Maps the query's `OrderByItem`s onto physical `OrderBy`
+/// expressions, resolving each item against the list of selected
+/// members so it can be rendered as a positional reference.
pub struct OrderPlanner {
query_properties: Rc,
}
@@ -12,6 +15,8 @@ impl OrderPlanner {
Self { query_properties }
}
+ /// Renders the request's `order_by` against the full set of
+ /// selected members.
pub fn default_order(&self) -> Vec {
Self::custom_order(
self.query_properties.order_by(),
@@ -19,6 +24,8 @@ impl OrderPlanner {
)
}
+ /// Resolves an explicit `order_by` list against an explicit
+ /// member list, producing positional `OrderBy` entries.
pub fn custom_order(order_by: &[OrderByItem], members: &[Rc]) -> Vec {
let mut result = Vec::new();
for itm in order_by.iter() {
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/query_planner.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/query_planner.rs
index 72857199e0efd..1a41e59325926 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/query_planner.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/query_planner.rs
@@ -9,6 +9,12 @@ use crate::planner::QueryProperties;
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// Entry point of the logical-plan construction. Dispatches by
+/// `QueryProperties::is_simple_query()`: simple queries go through
+/// `SimpleQueryPlanner`; everything else is built up from
+/// multi-stage and multiplied-measure CTEs (`MultiStageQueryPlanner`,
+/// `MultipliedMeasuresQueryPlanner`) and stitched together by
+/// `FullKeyAggregateQueryPlanner`.
pub struct QueryPlanner {
query_tools: Rc,
request: Rc,
@@ -22,6 +28,9 @@ impl QueryPlanner {
}
}
+ /// Dispatches to `SimpleQueryPlanner` for simple queries; otherwise
+ /// builds the multi-stage / multiplied CTEs and assembles them via
+ /// `FullKeyAggregateQueryPlanner`.
pub fn plan(&self) -> Result, CubeError> {
if self.request.is_simple_query()? {
let planner = SimpleQueryPlanner::new(self.query_tools.clone(), self.request.clone());
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/simple_query_planer.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/simple_query_planer.rs
index bbd98b4cde749..6572000af9ba2 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/simple_query_planer.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/simple_query_planer.rs
@@ -6,6 +6,9 @@ use crate::planner::QueryProperties;
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// Plans a `Query` for the simple case: a single `LogicalJoin`
+/// source, no multi-stage or multiplied CTEs. Sub-query dimensions
+/// are still woven into the join.
pub struct SimpleQueryPlanner {
query_tools: Rc,
query_properties: Rc,
@@ -20,6 +23,7 @@ impl SimpleQueryPlanner {
}
}
+ /// Builds the `Query` for a simple-case request.
pub fn plan(&self) -> Result, CubeError> {
let source = self.source_and_subquery_dimensions()?;
@@ -54,6 +58,8 @@ impl SimpleQueryPlanner {
Ok(Rc::new(result))
}
+ /// Resolves the query's join and the sub-query dimensions that
+ /// plug into it, returning the assembled `LogicalJoin` source.
pub fn source_and_subquery_dimensions(&self) -> Result, CubeError> {
let join = self.query_properties.simple_query_join()?;
let subquery_dimensions = if let Some(join) = &join {
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/query_properties.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/query_properties.rs
index 32e141429022d..3c4ab1e57d91c 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/query_properties.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/query_properties.rs
@@ -487,7 +487,7 @@ impl QueryProperties {
}
let is_multiplied_measure = if item.multiplied {
if let Ok(measure) = item.measure.as_measure() {
- if measure.can_used_as_addictive_in_multplied() {
+ if measure.is_additive_in_multiplied() {
false
} else {
true
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_call.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_call.rs
index 85063a0740733..2422cd83711a7 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_call.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_call.rs
@@ -11,6 +11,14 @@ use itertools::Itertools;
use std::collections::HashMap;
use std::rc::Rc;
+/// Reference to a cube from a SQL template.
+///
+/// - `Name` — the cube as an identifier (rendered from `{CUBE}` or
+/// `{TABLE}` placeholders); resolves to the cube's quoted name or
+/// alias.
+/// - `Table` — the cube's table expression (rendered from
+/// `{CUBE.sql()}`); resolves to the result of the cube's `sql:`
+/// function, or to a registered pre-aggregation source.
#[derive(Clone, Debug)]
pub enum CubeRef {
Name(Rc),
@@ -47,6 +55,8 @@ impl CubeRef {
}
}
+/// One `{arg:N}` binding inside a `SqlCall` template: either a
+/// member symbol or a cube reference.
#[derive(Clone, Debug)]
pub enum SqlDependency {
Symbol(Rc),
@@ -77,6 +87,13 @@ impl SqlDependency {
}
}
+/// Namespace for the placeholder prefixes recognised inside a
+/// `SqlCall` template:
+///
+/// - `arg:N` — Nth `SqlDependency` (member symbol or cube ref).
+/// - `fp:N` — Nth filter param.
+/// - `fg:N` — Nth filter group.
+/// - `sv:N` — Nth security-context value.
pub struct SqlCallArg;
impl SqlCallArg {
@@ -99,17 +116,32 @@ impl SqlCallArg {
}
}
+/// One `FILTER_PARAMS` binding from the data-model SQL: the filter
+/// member whose predicate should be substituted at render time,
+/// together with the column information used to format it.
#[derive(Debug, Clone)]
pub struct SqlCallFilterParamsItem {
pub filter_symbol_name: String,
pub column: FilterParamsColumn,
}
+/// `FILTER_GROUP` binding from the data-model SQL: several
+/// `FILTER_PARAMS` items combined into a single substitution.
#[derive(Clone, Debug)]
pub struct SqlCallFilterGroupItem {
pub filter_params: Vec,
}
+/// Tesseract representation of a SQL-like function declared in the
+/// data model (member `sql:`, `mask_sql:`, case branches, measure
+/// filters, and so on). Plays two roles: it stores the template's
+/// dependencies — already resolved to live member symbols and cube
+/// references, not symbolic paths — and it knows how to turn those
+/// dependencies into the final SQL. Placeholders `{arg:N}` /
+/// `{fp:N}` / `{fg:N}` / `{sv:N}` are substituted with the rendered
+/// SQL of the bound dependency, filter param, filter group or
+/// security-context value when the call is evaluated (`eval` /
+/// `eval_vec`).
#[derive(Clone, Debug)]
pub struct SqlCall {
template: SqlTemplate,
@@ -117,9 +149,9 @@ pub struct SqlCall {
filter_params: Vec,
filter_groups: Vec,
security_context: SecutityContextProps,
- /// Per `{arg:N}` index: whether the surrounding context in the template
- /// would make a compound substitution unsafe (requiring parentheses).
- /// Computed once at construction from the template.
+ // Per `{arg:N}` index: whether the surrounding context in the template
+ // would make a compound substitution unsafe (requiring parentheses).
+ // Computed once at construction from the template.
arg_paren_contexts: HashMap,
}
@@ -154,6 +186,8 @@ impl SqlCall {
}
}
+ /// Renders the template into a single SQL string. Errors when
+ /// the template is a `StringVec` — use `eval_vec` for that case.
pub fn eval(
&self,
visitor: &SqlEvaluatorVisitor,
@@ -179,6 +213,11 @@ impl SqlCall {
}
}
+ /// Renders the template into one SQL string per element when it
+ /// is a `StringVec`, or a single-element vector for plain
+ /// `String` templates. `StringVec` is produced by pre-aggregation
+ /// dimension / measure reference lists, where the data-model
+ /// definition returns the SQL of all referenced members at once.
pub fn eval_vec(
&self,
visitor: &SqlEvaluatorVisitor,
@@ -215,6 +254,9 @@ impl SqlCall {
Ok(result)
}
+ /// True when the SQL belongs to a single cube — either it has no
+ /// dependencies at all (a constant expression), or it references
+ /// the owning cube directly through a `{CUBE}` placeholder.
pub fn is_owned_by_cube(&self) -> bool {
if self.deps.is_empty() {
true
@@ -223,6 +265,8 @@ impl SqlCall {
}
}
+ /// All `CubeRef::Name` dependencies — cube-name placeholders the
+ /// template refers to.
pub fn cube_name_deps(&self) -> Vec> {
self.deps
.iter()
@@ -415,11 +459,16 @@ impl SqlCall {
.collect()
}
+ /// True if the entire template is exactly `{arg:0}` with a single
+ /// member-symbol dependency — i.e. the SQL is a plain reference
+ /// to another member, with no wrapping expression.
pub fn is_direct_reference(&self) -> bool {
self.dependencies_count() == 1
&& self.template == SqlTemplate::String(SqlCallArg::dependency(0))
}
+ /// The single member this call references when `is_direct_reference`
+ /// is true; `None` otherwise.
pub fn resolve_direct_reference(&self) -> Option> {
if self.is_direct_reference() {
self.deps[0].as_symbol().cloned()
@@ -428,10 +477,14 @@ impl SqlCall {
}
}
+ /// Number of member-symbol dependencies. Cube refs are not
+ /// counted.
pub fn dependencies_count(&self) -> usize {
self.deps.iter().filter(|d| d.is_symbol()).count()
}
+ /// Member-symbol dependencies of the call. Cube refs are excluded
+ /// — use `get_cube_refs` for those.
pub fn get_dependencies(&self) -> Vec> {
self.deps
.iter()
@@ -455,6 +508,9 @@ impl SqlCall {
}
}
+ /// Returns a new `SqlCall` with `f` applied recursively to every
+ /// member-symbol dependency. Cube refs and other placeholders
+ /// pass through unchanged.
pub fn apply_recursive) -> Result, CubeError>>(
&self,
f: &F,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_call_builder.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_call_builder.rs
index 5617bad221a07..cc27a56af3fee 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_call_builder.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_call_builder.rs
@@ -10,6 +10,10 @@ use crate::cube_bridge::security_context::SecurityContext;
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// Builds a `SqlCall` from a `MemberSql` declaration: compiles the
+/// template, resolves each path placeholder to a `SqlDependency`
+/// (member symbol or cube reference), and attaches filter params,
+/// filter groups and security-context bindings.
pub struct SqlCallBuilder<'a> {
compiler: &'a mut Compiler,
cube_evaluator: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/common/aggregation_type.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/common/aggregation_type.rs
index 346c3a1fdec94..c482de005862f 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/common/aggregation_type.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/common/aggregation_type.rs
@@ -1,5 +1,7 @@
use cubenativeutils::CubeError;
+/// Aggregation function declared on a measure in the data model
+/// (`sum`, `avg`, `min`, `max`, distinct counts, etc.).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AggregationType {
Sum,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/common/case.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/common/case.rs
index 83a75ec9dac1d..a4730b839fb28 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/common/case.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/common/case.rs
@@ -322,6 +322,14 @@ impl CaseSwitchDefinition {
}
}
+/// Body of a case-defined member, mapped from the `case` field of
+/// the data-model definition.
+///
+/// - `Case` — classic `CASE WHEN condition THEN label ELSE label END`.
+/// - `CaseSwitch` — switch-style `CASE switch WHEN value THEN sql
+/// ELSE sql END`. `switch` may resolve to a direct reference to
+/// another dimension (typically a `type: switch` dimension whose
+/// `values` drive the branches).
#[derive(Clone)]
pub enum Case {
Case(CaseDefinition),
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/common/dimension_type.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/common/dimension_type.rs
index a0a4c435e16e3..a603c80e937db 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/common/dimension_type.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/common/dimension_type.rs
@@ -1,5 +1,6 @@
use cubenativeutils::CubeError;
+/// Value type of a dimension as declared in the data model.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DimensionType {
String,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/common/symbol_path.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/common/symbol_path.rs
index 29f28fb0d56bc..be1e164a1d9b2 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/common/symbol_path.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/common/symbol_path.rs
@@ -14,6 +14,10 @@ pub enum SymbolPathType {
CubeTable,
}
+/// Resolved path of a member or cube reference in the data model:
+/// parsed from a dotted string (`cube.member`, `cube.cube2.dim`,
+/// `cube.time_dim.day`, `CUBE`, `CUBE.__sql_fn`), with its
+/// `SymbolPathType` and the chain of cubes traversed along the way.
#[derive(Debug, Clone)]
pub struct SymbolPath {
path_type: SymbolPathType,
@@ -47,11 +51,16 @@ impl SymbolPath {
}
}
+ /// Parses a dotted path string by walking the data model through
+ /// the provided `CubeEvaluator`.
pub fn parse(cube_evaluator: Rc, path: &str) -> Result {
let parts: Vec = path.split('.').map(|s| s.to_string()).collect();
Self::parse_parts(cube_evaluator, None, &parts)
}
+ /// Parses pre-split path segments resolved relative to
+ /// `current_cube` — so the first segment may refer to a member
+ /// of `current_cube` directly.
pub fn parse_parts(
cube_evaluator: Rc,
current_cube: Option<&str>,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/cube_symbol.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/cube_symbol.rs
index 33cb7c129c236..a47a0926582aa 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/cube_symbol.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/cube_symbol.rs
@@ -11,6 +11,8 @@ use lazy_static::lazy_static;
use regex::Regex;
use std::rc::Rc;
+/// Symbol for a cube referenced as an identifier (`{CUBE}` /
+/// `{TABLE}`); renders to the cube's quoted name or alias.
#[derive(Debug)]
pub struct CubeNameSymbol {
cube_name: String,
@@ -70,6 +72,9 @@ impl CubeNameSymbolFactory {
}
}
+/// Symbol for a cube referenced as a table expression
+/// (`{CUBE.sql()}`); renders by evaluating the cube's `sql:`
+/// function or its raw `sql_table:` value.
#[derive(Debug)]
pub struct CubeTableSymbol {
cube_name: String,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/case_dimension.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/case_dimension.rs
index 9be1340ff60bc..262d2421e64e3 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/case_dimension.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/case_dimension.rs
@@ -4,6 +4,9 @@ use crate::planner::{CubeRef, SqlCall};
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// Dimension whose value is defined via the `case` field of the
+/// data-model definition. Comes in classic and switch-style forms;
+/// see `Case`.
#[derive(Clone)]
pub struct CaseDimension {
dimension_type: DimensionType,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/geo.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/geo.rs
index 2b1ddad336b68..07ead104bf554 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/geo.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/geo.rs
@@ -3,6 +3,8 @@ use crate::planner::{CubeRef, SqlCall};
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// `type: geo` dimension from the data model: a geographic dimension
+/// defined by `latitude` and `longitude` SQL expressions.
#[derive(Clone)]
pub struct GeoDimension {
latitude: Rc,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/mod.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/mod.rs
index 0cab09d97aa03..defaaf27aab85 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/mod.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/mod.rs
@@ -14,6 +14,16 @@ use crate::planner::{CubeRef, SqlCall};
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// Form of a dimension's value, classified from its data-model
+/// definition.
+///
+/// - `Regular` — plain dimension (a single `sql` expression).
+/// - `Geo` — `type: geo` dimension with `latitude` / `longitude`.
+/// - `Switch` — `type: switch` dimension; without a `sql` it becomes
+/// a **calc group** — an abstract enumeration cross-joined into the
+/// query rather than read from a column.
+/// - `Case` — dimension defined via a `case` body, classic or
+/// switch-style.
#[derive(Clone)]
pub enum DimensionKind {
Regular(RegularDimension),
@@ -91,6 +101,9 @@ impl DimensionKind {
matches!(self, Self::Case(_))
}
+ /// True for a `Switch` dimension declared without a `sql` — a
+ /// calc group: an abstract enumeration cross-joined into the query
+ /// rather than read from a column.
pub fn is_calc_group(&self) -> bool {
match self {
Self::Switch(s) => s.is_calc_group(),
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/regular.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/regular.rs
index a1fcf9d056e95..a75e0c44fe477 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/regular.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/regular.rs
@@ -4,6 +4,8 @@ use crate::planner::{CubeRef, SqlCall};
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// Plain dimension from the data model — a single `sql` expression
+/// typed by `DimensionType`.
#[derive(Clone)]
pub struct RegularDimension {
dimension_type: DimensionType,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/switch.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/switch.rs
index 6560365ce6877..be378c9cb57ec 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/switch.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_kinds/switch.rs
@@ -3,6 +3,11 @@ use crate::planner::{CubeRef, SqlCall};
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// `type: switch` dimension from the data model: an enum with a
+/// fixed list of allowed string values. With a `sql` — an ordinary
+/// enum dimension reading from a real column. Without a `sql` — a
+/// **calc group**: an abstract enumeration cross-joined into the
+/// query as a virtual table of values.
#[derive(Clone)]
pub struct SwitchDimension {
values: Vec,
@@ -22,6 +27,9 @@ impl SwitchDimension {
self.member_sql.as_ref()
}
+ /// True when the switch dimension was declared without a `sql` —
+ /// a calc group: an abstract enumeration cross-joined into the
+ /// query rather than read from a column.
pub fn is_calc_group(&self) -> bool {
self.member_sql.is_none()
}
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_symbol.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_symbol.rs
index f715a086ce876..63901340e23f9 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_symbol.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/dimension_symbol.rs
@@ -16,6 +16,9 @@ use crate::planner::{Compiler, CubeRef, SqlCall};
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// Time-shift entry on a dimension of a calendar cube: shifts the
+/// date range by either a fixed interval, a named slot (e.g.
+/// `prev_year`), or a custom SQL expression.
#[derive(Clone)]
pub struct CalendarDimensionTimeShift {
pub interval: Option,
@@ -23,6 +26,9 @@ pub struct CalendarDimensionTimeShift {
pub sql: Option>,
}
+/// `MemberSymbol::Dimension` body: Tesseract representation of a
+/// `dimension` declared in the data model — a value the query can
+/// group, filter or order by, but never aggregate.
#[derive(Clone)]
pub struct DimensionSymbol {
compiled_path: CompiledMemberPath,
@@ -74,6 +80,8 @@ impl DimensionSymbol {
self.kind.is_calc_group()
}
+ /// String values declared on a `Switch` dimension; empty for any
+ /// other kind.
pub fn values(&self) -> &[String] {
match &self.kind {
DimensionKind::Switch(s) => s.values(),
@@ -93,6 +101,7 @@ impl DimensionSymbol {
Rc::new(new)
}
+ /// Case-expression body for `DimensionKind::Case`; `None` otherwise.
pub fn case(&self) -> Option<&Case> {
match &self.kind {
DimensionKind::Case(c) => Some(c.case()),
@@ -100,6 +109,7 @@ impl DimensionSymbol {
}
}
+ /// `None` if the dimension has no primary SQL expression of its own.
pub fn member_sql(&self) -> Option<&Rc> {
match &self.kind {
DimensionKind::Regular(r) => Some(r.member_sql()),
@@ -121,18 +131,29 @@ impl DimensionSymbol {
&self.compiled_path
}
+ /// Trims the join-chain prefix from `compiled_path` in place so the
+ /// path points only at the owning cube.
pub fn strip_join_prefix(&mut self) {
self.compiled_path = self.compiled_path.strip_join_prefix();
}
+ /// Full unique identifier of the symbol: cube path, member name and
+ /// any suffix that distinguishes one symbol from another.
pub fn full_name(&self) -> String {
self.compiled_path.full_name().clone()
}
+ /// Default alias of the dimension, derived from the compiled member
+ /// path.
pub fn alias(&self) -> String {
self.compiled_path.alias().clone()
}
+ /// True when the cube on the symbol's path actually owns this
+ /// dimension — the cube is required in the join to read the
+ /// dimension from the database. False for view-exposed dimensions,
+ /// multi-stage dimensions, switches, and members defined as pure
+ /// compositions of other members (no `{CUBE}` references).
pub fn owned_by_cube(&self) -> bool {
!self.is_multi_stage && !self.kind.is_switch() && self.kind.is_owned_by_cube()
}
@@ -141,10 +162,14 @@ impl DimensionSymbol {
self.is_multi_stage
}
+ /// Direct mapping from the `sub_query` field of the dimension
+ /// definition in the data model.
pub fn is_sub_query(&self) -> bool {
self.is_sub_query
}
+ /// Optional SQL expression that wraps the dimension's rendered
+ /// output to mask its value (data hiding / column-level masking).
pub fn mask_sql(&self) -> &Option> {
&self.mask_sql
}
@@ -177,6 +202,8 @@ impl DimensionSymbol {
self.kind.is_case()
}
+ /// Direct mapping from the `propagate_filters_to_sub_query` field
+ /// of the dimension definition in the data model.
pub fn propagate_filters_to_sub_query(&self) -> bool {
self.propagate_filters_to_sub_query
}
@@ -189,6 +216,8 @@ impl DimensionSymbol {
self.is_view
}
+ /// The member this dimension references, or `None` if it is not a
+ /// reference.
pub fn reference_member(&self) -> Option> {
if !self.is_reference() {
return None;
@@ -212,16 +241,16 @@ impl DimensionSymbol {
Ok(MemberSymbol::new_dimension(Rc::new(result)))
}
+ /// SQL calls inside the kind body. `mask_sql` is intentionally
+ /// excluded: it is compiled against the cube that owns the
+ /// dimension, which differs from the symbol's own `cube_name` when
+ /// the dimension is exposed through a view. Including it in
+ /// cube-ref validation would produce false foreign-cube errors.
pub fn iter_sql_calls(&self) -> Box> + '_> {
- // mask_sql is intentionally excluded here: it's compiled in the
- // context of the cube that owns the dimension (via aliasMember when
- // the dimension is exposed through a view), which may legitimately
- // differ from the current cube_name of the symbol. Including it in
- // the generic validate_regular_member_cube_refs would produce false
- // foreign-cube errors for view members.
self.kind.iter_sql_calls()
}
+ /// All member dependencies of the dimension.
pub fn get_dependencies(&self) -> Vec> {
let mut deps = self.kind.get_dependencies();
if let Some(mask) = &self.mask_sql {
@@ -230,6 +259,7 @@ impl DimensionSymbol {
deps
}
+ /// All cube references of the dimension.
pub fn get_cube_refs(&self) -> Vec {
let mut refs = self.kind.get_cube_refs();
if let Some(mask) = &self.mask_sql {
@@ -254,6 +284,9 @@ impl DimensionSymbol {
self.compiled_path.path()
}
+ /// Finds the calendar time-shift defined for the exact `interval`
+ /// and returns it together with the primary-key full name. `None`
+ /// when either the matching shift or the primary key is missing.
pub fn calendar_time_shift_for_interval(
&self,
interval: &SqlInterval,
@@ -272,6 +305,10 @@ impl DimensionSymbol {
None
}
+ /// Finds the named calendar time-shift and returns it together
+ /// with the primary-key full name. Falls back to this dimension's
+ /// own full name when the dimension is itself the calendar primary
+ /// key.
pub fn calendar_time_shift_for_named_interval(
&self,
interval_name: &String,
@@ -293,6 +330,9 @@ impl DimensionSymbol {
}
}
+/// Builds a `DimensionSymbol` from a dimension definition pulled out
+/// of the cube schema. When the requested path includes a granularity,
+/// the result is wrapped in a `TimeDimensionSymbol` instead.
pub struct DimensionSymbolFactory {
path: SymbolPath,
sql: Option>,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_kinds/aggregated.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_kinds/aggregated.rs
index d2cda76e7a09e..732e679a93f97 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_kinds/aggregated.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_kinds/aggregated.rs
@@ -4,6 +4,8 @@ use crate::planner::{CubeRef, SqlCall};
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// `Aggregated` measure kind. `sql` may be absent when the measure
+/// is built from a `case` body.
#[derive(Clone)]
pub struct AggregatedMeasure {
agg_type: AggregationType,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_kinds/calculated.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_kinds/calculated.rs
index bf7d74ef95c22..97992769070d0 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_kinds/calculated.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_kinds/calculated.rs
@@ -3,6 +3,8 @@ use crate::planner::{CubeRef, SqlCall};
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// Value type of a calculated (non-aggregating) measure as declared
+/// in the data model.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CalculatedMeasureType {
Number,
@@ -32,6 +34,8 @@ impl CalculatedMeasureType {
}
}
+/// `Calculated` measure kind: a non-aggregating expression. `sql`
+/// may be absent when the value is provided through a `case` body.
#[derive(Clone)]
pub struct CalculatedMeasure {
calc_type: CalculatedMeasureType,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_kinds/count.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_kinds/count.rs
index 83ad06e447717..1a7706b89e425 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_kinds/count.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_kinds/count.rs
@@ -3,12 +3,20 @@ use crate::planner::{CubeRef, SqlCall};
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// Source of a `Count` measure's SQL.
+///
+/// - `Auto` — no explicit `sql` was declared; the count falls back
+/// to the cube's primary-key expressions.
+/// - `Explicit` — `sql` was declared on the measure.
#[derive(Clone)]
pub enum CountSql {
Auto(Vec>),
Explicit(Rc),
}
+/// `Count` measure kind: counts rows of the underlying source.
+/// Without an explicit `sql` falls back to counting the cube's
+/// primary-key tuples.
#[derive(Clone)]
pub struct CountMeasure {
sql: CountSql,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_kinds/mod.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_kinds/mod.rs
index 17be5c1f2e305..d2529a4895f57 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_kinds/mod.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_kinds/mod.rs
@@ -12,6 +12,9 @@ use crate::planner::{CubeRef, SqlCall};
use cubenativeutils::CubeError;
use std::rc::Rc;
+/// How a measure kind wraps its inner SQL when rendered: no wrapper
+/// at all, a named SQL aggregate function, or one of the distinct-
+/// count special forms.
pub enum AggregateWrap<'a> {
PassThrough,
Function(&'a str),
@@ -19,6 +22,17 @@ pub enum AggregateWrap<'a> {
CountDistinctApprox,
}
+/// Form of a measure's aggregation, classified from the data-model
+/// `type`.
+///
+/// - `Count` — `type: count`. Counts rows; falls back to the cube's
+/// primary keys when no explicit `sql` is given.
+/// - `Aggregated` — built-in aggregation (`sum`, `avg`, `min`, `max`,
+/// `count_distinct`, `count_distinct_approx`, `number_agg`,
+/// `running_total`).
+/// - `Calculated` — `type: number / string / time / boolean`. A
+/// plain expression with no aggregation wrapper.
+/// - `Rank` — `type: rank`. Window-function rank, no `sql`.
#[derive(Clone)]
pub enum MeasureKind {
Count(CountMeasure),
@@ -112,6 +126,9 @@ impl MeasureKind {
matches!(self, Self::Calculated(_))
}
+ /// True if the kind's aggregation distributes over row union.
+ /// Counts are always additive; aggregated measures delegate to
+ /// their `AggregationType`. Calculated and rank are not additive.
pub fn is_additive(&self) -> bool {
match self {
Self::Count(_) => true,
@@ -129,6 +146,11 @@ impl MeasureKind {
}
}
+ /// True when `new_type` is a compatible aggregation replacement.
+ /// Only `Aggregated` measures can have their type replaced, and
+ /// only within compatibility groups: `sum`/`avg`/`min`/`max` are
+ /// interchangeable among themselves, distinct counts among
+ /// themselves.
pub fn can_replace_type_with(&self, new_type: &str) -> bool {
match self {
Self::Aggregated(a) => {
@@ -151,6 +173,9 @@ impl MeasureKind {
}
}
+ /// True if extra `measure_filters` can be merged into the kind.
+ /// Counts and the basic aggregations support it; `number_agg`,
+ /// `running_total`, calculated and rank measures do not.
pub fn supports_additional_filters(&self) -> bool {
match self {
Self::Count(_) => true,
@@ -179,6 +204,10 @@ impl MeasureKind {
}
}
+ /// How the kind wraps its inner SQL when rendered as a top-level
+ /// query measure. `is_multiplied` is true when the join below the
+ /// measure can produce duplicate rows — non-distinct counts then
+ /// switch to `count_distinct` over primary keys to stay correct.
pub fn aggregate_wrap(&self, is_multiplied: bool) -> AggregateWrap<'_> {
match self {
Self::Calculated(_) => AggregateWrap::PassThrough,
@@ -200,6 +229,10 @@ impl MeasureKind {
}
}
+ /// How the kind wraps its inner SQL when rolled up from a
+ /// pre-aggregation. Counts and most aggregations roll up via
+ /// `sum`; `min` / `max` preserve themselves; calculated string /
+ /// time / boolean values roll up via `max`.
pub fn pre_aggregate_wrap(&self) -> AggregateWrap<'_> {
match self {
Self::Count(_) => AggregateWrap::Function("sum"),
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_symbol.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_symbol.rs
index 20bb039c75f69..ac192d73dc3dd 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_symbol.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_symbol.rs
@@ -15,6 +15,7 @@ use std::cmp::{Eq, PartialEq};
use std::collections::HashMap;
use std::rc::Rc;
+/// Per-measure `order_by` entry from the data-model definition: a
#[derive(Clone)]
pub struct MeasureOrderBy {
sql_call: Rc,
@@ -42,6 +43,9 @@ impl MeasureOrderBy {
}
}
+/// Time-shift entry attached to a specific time dimension. Shifts
+/// that dimension's date range by either a fixed interval or a named
+/// slot.
#[derive(Clone, Debug)]
pub struct DimensionTimeShift {
pub interval: Option,
@@ -59,6 +63,13 @@ impl PartialEq for DimensionTimeShift {
impl Eq for DimensionTimeShift {}
+/// Form of a measure's `time_shift` declaration.
+///
+/// - `Dimensions` — one or more shifts, each bound to a specific time
+/// dimension.
+/// - `Common` — a single interval applied to every time dimension in
+/// the query.
+/// - `Named` — a single named slot applied to every time dimension.
#[derive(Clone, Debug)]
pub enum MeasureTimeShifts {
Dimensions(Vec),
@@ -66,6 +77,9 @@ pub enum MeasureTimeShifts {
Named(String),
}
+/// `MemberSymbol::Measure` body: Tesseract representation of a
+/// `measure` declared in the data model — an aggregation, count or
+/// calculated value the query exposes.
#[derive(Clone)]
pub struct MeasureSymbol {
compiled_path: CompiledMemberPath,
@@ -82,7 +96,6 @@ pub struct MeasureSymbol {
reduce_by: Option>>,
add_group_by: Option>>,
group_by: Option>>,
- is_splitted_source: bool,
mask_sql: Option>,
}
@@ -116,7 +129,6 @@ impl MeasureSymbol {
measure_order_by,
is_multi_stage,
time_shift,
- is_splitted_source: false,
reduce_by,
add_group_by,
group_by,
@@ -124,6 +136,12 @@ impl MeasureSymbol {
})
}
+ /// Returns a non-rolling copy of the symbol. A rolling-window
+ /// measure carries both the windowing context and the SQL of the
+ /// inner value it operates on; unrolling drops the window and
+ /// yields that inner value. Multi-stage rolling measures collapse
+ /// to a `Calculated` kind so they can be rendered without window-
+ /// function machinery.
pub fn new_unrolling(&self) -> Rc {
if self.is_rolling_window() {
let kind = if self.is_multi_stage {
@@ -155,7 +173,6 @@ impl MeasureSymbol {
reduce_by: self.reduce_by.clone(),
add_group_by: self.add_group_by.clone(),
group_by: self.group_by.clone(),
- is_splitted_source: self.is_splitted_source,
mask_sql: self.mask_sql.clone(),
})
} else {
@@ -163,6 +180,9 @@ impl MeasureSymbol {
}
}
+ /// Returns a copy of the symbol with the measure type optionally
+ /// replaced (subject to per-kind compatibility checks) and
+ /// additional measure filters merged in.
pub fn new_patched(
&self,
new_measure_type: Option,
@@ -208,7 +228,6 @@ impl MeasureSymbol {
reduce_by: self.reduce_by.clone(),
add_group_by: self.add_group_by.clone(),
group_by: self.group_by.clone(),
- is_splitted_source: self.is_splitted_source,
mask_sql: self.mask_sql.clone(),
}))
}
@@ -223,22 +242,24 @@ impl MeasureSymbol {
&self.compiled_path
}
+ /// Trims the join-chain prefix from `compiled_path` in place so
+ /// the path points only at the owning cube.
pub fn strip_join_prefix(&mut self) {
self.compiled_path = self.compiled_path.strip_join_prefix();
}
+ /// Full unique identifier of the symbol: cube path, member name
+ /// and any suffix that distinguishes one symbol from another.
pub fn full_name(&self) -> String {
self.compiled_path.full_name().clone()
}
+ /// Default alias of the measure, derived from the compiled member
+ /// path.
pub fn alias(&self) -> String {
self.compiled_path.alias().clone()
}
- pub fn is_splitted_source(&self) -> bool {
- self.is_splitted_source
- }
-
pub fn time_shift(&self) -> &Option {
&self.time_shift
}
@@ -251,11 +272,16 @@ impl MeasureSymbol {
self.case.as_ref()
}
+ /// Optional SQL expression that wraps the measure's rendered
+ /// output to mask its value (data hiding / column-level masking).
pub fn mask_sql(&self) -> &Option> {
&self.mask_sql
}
- pub fn is_addictive(&self) -> bool {
+ /// True when the measure's aggregation distributes over row union
+ /// (sum-like). Multi-stage measures are never additive — their
+ /// value depends on the windowed stage, not on a plain sum.
+ pub fn is_additive(&self) -> bool {
if self.is_multi_stage() {
false
} else {
@@ -293,15 +319,14 @@ impl MeasureSymbol {
Ok(MemberSymbol::new_measure(Rc::new(result)))
}
+ /// SQL calls inside the measure's kind and `case` body.
+ /// `mask_sql` is intentionally excluded: it is compiled against
+ /// the cube that owns the measure, which differs from the symbol's
+ /// own `cube_name` when the measure is exposed through a view.
+ /// `measure_filters` and `measure_order_by` are also skipped here
+ /// — the legacy BaseQuery validator does not check them, and we
+ /// preserve that behaviour for compatibility.
pub fn iter_sql_calls(&self) -> Box> + '_> {
- //FIXME We don't include filters and order_by here for backward compatibility
- // because BaseQuery doesn't validate these SQL calls
- // mask_sql is intentionally excluded here: it's compiled in the
- // context of the cube that owns the measure (via aliasMember when
- // the measure is exposed through a view), which may legitimately
- // differ from the current cube_name of the symbol. Including it in
- // the generic validate_regular_member_cube_refs would produce false
- // foreign-cube errors for view members.
let result = self
.kind
.iter_sql_calls()
@@ -349,7 +374,11 @@ impl MeasureSymbol {
refs
}
- pub fn can_used_as_addictive_in_multplied(&self) -> bool {
+ /// True if the measure stays correct when joined into a context
+ /// that multiplies rows (one-to-many join). Distinct aggregations
+ /// are naturally immune; primary-key-based counts (`type: count`
+ /// without an explicit `sql`) are too.
+ pub fn is_additive_in_multiplied(&self) -> bool {
match &self.kind {
MeasureKind::Aggregated(agg) => agg.agg_type().is_distinct(),
MeasureKind::Count(count) => count.is_owned_by_cube(),
@@ -357,6 +386,10 @@ impl MeasureSymbol {
}
}
+ /// True when the cube on the symbol's path is required in the
+ /// join to read the measure from the database. Multi-stage
+ /// measures are never owned by a cube; otherwise ownership is the
+ /// union of the kind, the measure filters and the `case` body.
pub fn owned_by_cube(&self) -> bool {
if self.is_multi_stage {
return false;
@@ -382,6 +415,8 @@ impl MeasureSymbol {
self.is_view
}
+ /// The member this measure references, or `None` if it is not a
+ /// reference.
pub fn reference_member(&self) -> Option> {
if !self.is_reference() {
return None;
@@ -413,6 +448,8 @@ impl MeasureSymbol {
matches!(&self.kind, MeasureKind::Aggregated(a) if a.agg_type() == AggregationType::RunningTotal)
}
+ /// True for rolling-window measures and running-total
+ /// aggregations.
pub fn is_cumulative(&self) -> bool {
self.is_rolling_window() || self.is_running_total()
}
@@ -462,6 +499,8 @@ impl MeasureSymbol {
}
}
+/// Builds a `MeasureSymbol` from a measure definition pulled out of
+/// the cube schema.
pub struct MeasureSymbolFactory {
path: SymbolPath,
sql: Option>,
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/member_expression_symbol.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/member_expression_symbol.rs
index 18f93065e243f..1f42462541f6d 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/member_expression_symbol.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/member_expression_symbol.rs
@@ -8,12 +8,22 @@ use cubenativeutils::CubeError;
use itertools::Itertools;
use std::rc::Rc;
+/// Body of a member expression.
+///
+/// - `SqlCall` — an arbitrary SQL expression provided directly by the
+/// query input.
+/// - `PatchedSymbol` — an existing member with query-time
+/// modifications applied on top.
#[derive(Clone)]
pub enum MemberExpressionExpression {
SqlCall(Rc),
PatchedSymbol(Rc),
}
+/// `MemberSymbol::MemberExpression` body: a synthetic member built
+/// at query time from a SQL expression or from another member with
+/// query-time modifications. Not declared in the data model. Its
+/// full name lives in the `expr:` namespace.
#[derive(Clone)]
pub struct MemberExpressionSymbol {
compiled_path: CompiledMemberPath,
@@ -49,6 +59,8 @@ impl MemberExpressionSymbol {
}))
}
+ /// Returns a copy of the symbol marked as parenthesized when
+ /// rendered.
pub fn with_parenthesized(self: &Rc) -> Rc {
let mut result = self.as_ref().clone();
result.parenthesized = true;
@@ -67,14 +79,20 @@ impl MemberExpressionSymbol {
&self.compiled_path
}
+ /// Trims the join-chain prefix from `compiled_path` in place so
+ /// the path points only at the owning cube.
pub fn strip_join_prefix(&mut self) {
self.compiled_path = self.compiled_path.strip_join_prefix();
}
+ /// Full unique identifier of the symbol; lives in the `expr:`
+ /// namespace to keep it disjoint from data-model member names.
pub fn full_name(&self) -> String {
self.compiled_path.full_name().clone()
}
+ /// Default alias of the expression, derived from the compiled
+ /// member path.
pub fn alias(&self) -> String {
self.compiled_path.alias().clone()
}
@@ -83,6 +101,9 @@ impl MemberExpressionSymbol {
self.is_reference
}
+ /// The member this expression references, or `None` if it is not
+ /// a reference. An expression is a reference only when its body
+ /// is a `SqlCall` that is itself a direct member reference.
pub fn reference_member(&self) -> Option> {
if !self.is_reference() {
return None;
@@ -133,6 +154,10 @@ impl MemberExpressionSymbol {
refs
}
+ /// If every leaf member referenced by the expression is a
+ /// dimension, returns the list of cube names those dimensions
+ /// belong to. Returns `None` if any leaf is a measure or other
+ /// non-dimension member.
pub fn cube_names_if_dimension_only_expression(
self: Rc,
) -> Result>, CubeError> {
diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/member_symbol.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/member_symbol.rs
index 0fe37af8902d1..7e043285de13c 100644
--- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/member_symbol.rs
+++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/member_symbol.rs
@@ -1,7 +1,6 @@
use cubenativeutils::CubeError;
use itertools::Itertools;
-use crate::planner::collectors::has_multi_stage_members;
use crate::planner::{Case, CubeRef, SqlCall};
use super::common::CompiledMemberPath;
@@ -9,6 +8,21 @@ use super::{DimensionSymbol, MeasureSymbol, MemberExpressionSymbol, TimeDimensio
use std::fmt::Debug;
use std::rc::Rc;
+/// First-class business object of the planner: the atomic unit of
+/// query planning, identifying one thing the query can select, filter,
+/// group or order by. The same `MemberSymbol` value carries through
+/// every layer — logical planning, physical-plan construction and SQL
+/// rendering.
+///
+/// A symbol is either bound to the data model — `Dimension` / `Measure`
+/// declared on a cube — or derived at query time: `TimeDimension`
+/// (a dimension at a chosen granularity and date range) or
+/// `MemberExpression` (synthetic, built from a SQL expression or a
+/// patched symbol). Identity is `full_name` + variant.
+///
+/// Indivisible: renders as a single SQL expression. A symbol may depend
+/// on other symbols (`get_dependencies`); whether those deps are
+/// inlined or pushed into a CTE / subquery is a physical-plan decision.
pub enum MemberSymbol {
Dimension(Rc),
TimeDimension(Rc),
@@ -66,10 +80,14 @@ impl MemberSymbol {
}
}
+ /// Full unique identifier of the symbol: cube path, member name
+ /// and any suffix that distinguishes one symbol from another.
pub fn full_name(&self) -> String {
self.compiled_path().full_name().clone()
}
+ /// Optional SQL expression that wraps the rendered member output to
+ /// mask its value (data hiding / column-level masking).
pub fn mask_sql(&self) -> Option<&Rc> {
match self {
Self::Dimension(d) => d.mask_sql().as_ref(),
@@ -95,6 +113,7 @@ impl MemberSymbol {
self.compiled_path().path()
}
+ /// Join-path metadata proxied from the owning cube definition.
pub fn join_map(&self) -> &Option>> {
self.compiled_path().join_map()
}
@@ -108,6 +127,7 @@ impl MemberSymbol {
}
}
+ /// Case-expression body, if the member is declared via `case:`.
pub fn case(&self) -> Option<&Case> {
match self {
MemberSymbol::Dimension(dimension_symbol) => dimension_symbol.case(),
@@ -123,10 +143,13 @@ impl MemberSymbol {
matches!(self, Self::Measure(_))
}
+ /// True for both `Dimension` and `TimeDimension`.
pub fn is_dimension(&self) -> bool {
matches!(self, Self::Dimension(_) | Self::TimeDimension(_))
}
+ /// Applies `f` to this symbol, then recurses into the dependencies of
+ /// the result returned by `f` — not of the original symbol.
pub fn apply_recursive) -> Result, CubeError>>(
self: &Rc,
f: &F,
@@ -165,6 +188,8 @@ impl MemberSymbol {
}
}
+ /// True if the symbol is a transparent alias for another member, with
+ /// no calculation of its own.
pub fn is_reference(&self) -> bool {
match self {
Self::Dimension(d) => d.is_reference(),
@@ -174,6 +199,7 @@ impl MemberSymbol {
}
}
+ /// The member this one references, or `None` if it is not a reference.
pub fn reference_member(&self) -> Option> {
match self {
Self::Dimension(d) => d.reference_member(),
@@ -183,6 +209,8 @@ impl MemberSymbol {
}
}
+ /// Follows `reference_member` repeatedly and returns the first symbol
+ /// in the chain that is not itself a reference.
pub fn resolve_reference_chain(self: Rc) -> Rc {
let mut current = self;
while let Some(reference) = current.reference_member() {
@@ -191,6 +219,8 @@ impl MemberSymbol {
current
}
+ /// True if `member` is this symbol or any symbol reachable via
+ /// `reference_member`. Self is included.
pub fn has_member_in_reference_chain(&self, member: &Rc) -> bool {
if self.full_name() == member.full_name() {
return true;
@@ -233,6 +263,8 @@ impl MemberSymbol {
}
}
+ /// `MemberExpression` symbols are never owned by a cube; for the other
+ /// variants, the answer comes from the underlying member definition.
pub fn owned_by_cube(&self) -> bool {
match self {
Self::Dimension(d) => d.owned_by_cube(),
@@ -282,6 +314,8 @@ impl MemberSymbol {
}
}
+ /// Granularity suffix appended to the alias of `TimeDimension`; `None`
+ /// for all other variants.
pub fn alias_suffix(&self) -> Option