From d3affd5a464c384cb27edfe3bdcbbfd78ba19983 Mon Sep 17 00:00:00 2001 From: Aleksandr Romanenko Date: Thu, 27 Nov 2025 18:03:50 +0100 Subject: [PATCH 01/12] chore(tesseract): Owned by cube logic and validation for symbols --- .../src/tests/cube_evaluator/mod.rs | 1 + .../src/tests/cube_evaluator/owned_by_cube.rs | 65 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs diff --git a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/mod.rs b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/mod.rs index 555e0e4be93d1..756337fe54785 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/mod.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/mod.rs @@ -1,2 +1,3 @@ mod compilation; +mod owned_by_cube; mod symbol_evaluator; diff --git a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs new file mode 100644 index 0000000000000..2e2dfa5bf13ed --- /dev/null +++ b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs @@ -0,0 +1,65 @@ +use crate::test_fixtures::cube_bridge::MockSchema; +use crate::test_fixtures::test_utils::TestContext; +use indoc::indoc; + +fn create_ownership_test_schema() -> MockSchema { + let yaml = indoc! {r#" + cubes: + - name: users + sql: "SELECT 1" + joins: + - name: orders + relationship: one_to_many + sql: "{users}.id = {orders.user_id}" + dimensions: + - name: id + type: number + sql: id + primary_key: true + - name: userName + type: string + sql: "{CUBE}.user_name" + - name: userNameProxy + type: string + sql: "{CUBE.user_name}" + - name: secondId + type: number + sql: "{CUBE}.secondId" + - name: complexId + type: number + sql: "{id} % {users.secondId}" + measures: + - name: count + type: count + - name: orders + sql: "SELECT 1" + dimensions: + - name: id + type: number + sql: id + primary_key: true + - name: orderUserName + type: string + sql: "{users.userName}" + measures: + - name: count + type: count + views: + - name: users_to_orders + cubes: + - join_path: users + includes: "*" + - join_path: users.orders + includes: + - orderUserName + "#}; + MockSchema::from_yaml(yaml).unwrap() +} + +#[test] +fn dimensions_ownships() { + let schema = create_ownership_test_schema(); + let context = TestContext::new(schema).unwrap(); + let symbol = context.create_dimension("users.id").unwrap(); + assert!(symbol.owned_by_cube()); +} From 59cb38fdeef55c3c3a8e988efae5f9b0c055a3b1 Mon Sep 17 00:00:00 2001 From: Aleksandr Romanenko Date: Thu, 20 Nov 2025 18:41:27 +0100 Subject: [PATCH 02/12] yaml fixtures --- .../test_fixtures/cube_bridge/mock_schema.rs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/cube_bridge/mock_schema.rs b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/cube_bridge/mock_schema.rs index 667f4150c94cd..f5564ade7780e 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/cube_bridge/mock_schema.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/cube_bridge/mock_schema.rs @@ -32,15 +32,20 @@ impl MockSchema { /// The path is relative to `src/test_fixtures/schemas/yaml_files/`. /// For example, `"common/visitors.yaml"` loads from /// `src/test_fixtures/schemas/yaml_files/common/visitors.yaml`. +<<<<<<< HEAD /// /// Panics if the file cannot be read or parsed. pub fn from_yaml_file(relative_path: &str) -> Self { +======= + pub fn from_yaml_file(relative_path: &str) -> Result { +>>>>>>> 1b92358ce4 (yaml fixtures) let manifest_dir = env!("CARGO_MANIFEST_DIR"); let full_path = format!( "{}/src/test_fixtures/schemas/yaml_files/{}", manifest_dir, relative_path ); +<<<<<<< HEAD let yaml = std::fs::read_to_string(&full_path) .unwrap_or_else(|e| panic!("Failed to read YAML fixture '{}': {}", relative_path, e)); @@ -50,6 +55,16 @@ impl MockSchema { relative_path, e.message ) }) +======= + let yaml = std::fs::read_to_string(&full_path).map_err(|e| { + CubeError::user(format!( + "Failed to read YAML fixture '{}': {}", + relative_path, e + )) + })?; + + Self::from_yaml(&yaml) +>>>>>>> 1b92358ce4 (yaml fixtures) } pub fn get_cube(&self, name: &str) -> Option<&MockCube> { @@ -1496,8 +1511,17 @@ mod tests { } #[test] +<<<<<<< HEAD #[should_panic(expected = "Failed to read YAML fixture")] fn test_from_yaml_file_not_found() { MockSchema::from_yaml_file("nonexistent.yaml"); +======= + fn test_from_yaml_file_not_found() { + let result = MockSchema::from_yaml_file("nonexistent.yaml"); + assert!(result.is_err()); + if let Err(e) = result { + assert!(e.message.contains("Failed to read YAML fixture")); + } +>>>>>>> 1b92358ce4 (yaml fixtures) } } From a2691b7dc249755e5db36713c9761600f18b8178 Mon Sep 17 00:00:00 2001 From: Aleksandr Romanenko Date: Thu, 27 Nov 2025 18:05:50 +0100 Subject: [PATCH 03/12] in work --- .../test_fixtures/cube_bridge/mock_schema.rs | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/cube_bridge/mock_schema.rs b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/cube_bridge/mock_schema.rs index f5564ade7780e..667f4150c94cd 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/cube_bridge/mock_schema.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/cube_bridge/mock_schema.rs @@ -32,20 +32,15 @@ impl MockSchema { /// The path is relative to `src/test_fixtures/schemas/yaml_files/`. /// For example, `"common/visitors.yaml"` loads from /// `src/test_fixtures/schemas/yaml_files/common/visitors.yaml`. -<<<<<<< HEAD /// /// Panics if the file cannot be read or parsed. pub fn from_yaml_file(relative_path: &str) -> Self { -======= - pub fn from_yaml_file(relative_path: &str) -> Result { ->>>>>>> 1b92358ce4 (yaml fixtures) let manifest_dir = env!("CARGO_MANIFEST_DIR"); let full_path = format!( "{}/src/test_fixtures/schemas/yaml_files/{}", manifest_dir, relative_path ); -<<<<<<< HEAD let yaml = std::fs::read_to_string(&full_path) .unwrap_or_else(|e| panic!("Failed to read YAML fixture '{}': {}", relative_path, e)); @@ -55,16 +50,6 @@ impl MockSchema { relative_path, e.message ) }) -======= - let yaml = std::fs::read_to_string(&full_path).map_err(|e| { - CubeError::user(format!( - "Failed to read YAML fixture '{}': {}", - relative_path, e - )) - })?; - - Self::from_yaml(&yaml) ->>>>>>> 1b92358ce4 (yaml fixtures) } pub fn get_cube(&self, name: &str) -> Option<&MockCube> { @@ -1511,17 +1496,8 @@ mod tests { } #[test] -<<<<<<< HEAD #[should_panic(expected = "Failed to read YAML fixture")] fn test_from_yaml_file_not_found() { MockSchema::from_yaml_file("nonexistent.yaml"); -======= - fn test_from_yaml_file_not_found() { - let result = MockSchema::from_yaml_file("nonexistent.yaml"); - assert!(result.is_err()); - if let Err(e) = result { - assert!(e.message.contains("Failed to read YAML fixture")); - } ->>>>>>> 1b92358ce4 (yaml fixtures) } } From 426755ce5140c4ea9decde1d7d8ffcf6b6dfa999 Mon Sep 17 00:00:00 2001 From: Aleksandr Romanenko Date: Thu, 20 Nov 2025 18:55:55 +0100 Subject: [PATCH 04/12] yaml fixtures --- .../owned_by_cube/ownership_test.yaml | 48 +++++++ .../symbol_evaluator/count_no_pk.yaml | 13 ++ .../symbol_evaluator/count_one_pk.yaml | 14 ++ .../symbol_evaluator/count_two_pk.yaml | 15 +++ .../symbol_evaluator/test_cube.yaml | 43 ++++++ .../src/tests/cube_evaluator/owned_by_cube.rs | 57 +------- .../tests/cube_evaluator/symbol_evaluator.rs | 123 +----------------- 7 files changed, 141 insertions(+), 172 deletions(-) create mode 100644 rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/owned_by_cube/ownership_test.yaml create mode 100644 rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/symbol_evaluator/count_no_pk.yaml create mode 100644 rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/symbol_evaluator/count_one_pk.yaml create mode 100644 rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/symbol_evaluator/count_two_pk.yaml create mode 100644 rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/symbol_evaluator/test_cube.yaml diff --git a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/owned_by_cube/ownership_test.yaml b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/owned_by_cube/ownership_test.yaml new file mode 100644 index 0000000000000..ab2a5120299ad --- /dev/null +++ b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/owned_by_cube/ownership_test.yaml @@ -0,0 +1,48 @@ +cubes: + - name: users + sql: "SELECT 1" + joins: + - name: orders + relationship: one_to_many + sql: "{users}.id = {orders.user_id}" + dimensions: + - name: id + type: number + sql: id + primary_key: true + - name: userName + type: string + sql: "{CUBE}.user_name" + - name: userNameProxy + type: string + sql: "{CUBE.user_name}" + - name: secondId + type: number + sql: "{CUBE}.secondId" + - name: complexId + type: number + sql: "{id} % {users.secondId}" + measures: + - name: count + type: count + - name: orders + sql: "SELECT 1" + dimensions: + - name: id + type: number + sql: id + primary_key: true + - name: orderUserName + type: string + sql: "{users.userName}" + measures: + - name: count + type: count +views: + - name: users_to_orders + cubes: + - join_path: users + includes: "*" + - join_path: users.orders + includes: + - orderUserName diff --git a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/symbol_evaluator/count_no_pk.yaml b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/symbol_evaluator/count_no_pk.yaml new file mode 100644 index 0000000000000..ac678cd6d6428 --- /dev/null +++ b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/symbol_evaluator/count_no_pk.yaml @@ -0,0 +1,13 @@ +cubes: + - name: users + sql: "SELECT 1" + dimensions: + - name: id + type: number + sql: id + - name: userName + type: string + sql: user_name + measures: + - name: count + type: count diff --git a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/symbol_evaluator/count_one_pk.yaml b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/symbol_evaluator/count_one_pk.yaml new file mode 100644 index 0000000000000..bd47f34286ca9 --- /dev/null +++ b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/symbol_evaluator/count_one_pk.yaml @@ -0,0 +1,14 @@ +cubes: + - name: users + sql: "SELECT 1" + dimensions: + - name: id + type: number + sql: id + primary_key: true + - name: userName + type: string + sql: user_name + measures: + - name: count + type: count diff --git a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/symbol_evaluator/count_two_pk.yaml b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/symbol_evaluator/count_two_pk.yaml new file mode 100644 index 0000000000000..92f72344dc1ee --- /dev/null +++ b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/symbol_evaluator/count_two_pk.yaml @@ -0,0 +1,15 @@ +cubes: + - name: users + sql: "SELECT 1" + dimensions: + - name: id + type: number + sql: id + primary_key: true + - name: userName + type: string + sql: user_name + primary_key: true + measures: + - name: count + type: count diff --git a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/symbol_evaluator/test_cube.yaml b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/symbol_evaluator/test_cube.yaml new file mode 100644 index 0000000000000..d57aee3971476 --- /dev/null +++ b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/symbol_evaluator/test_cube.yaml @@ -0,0 +1,43 @@ +cubes: + - name: test_cube + sql: "SELECT 1" + dimensions: + - name: id + type: number + sql: id + primary_key: true + - name: source + type: string + sql: "{CUBE}.source" + - name: source_extended + type: string + sql: "CONCAT({CUBE.source}, '_source')" + - name: created_at + type: time + sql: created_at + - name: location + type: geo + latitude: latitude + longitude: longitude + measures: + - name: sum_revenue + type: sum + sql: revenue + - name: min_revenue + type: min + sql: revenue + - name: max_revenue + type: max + sql: revenue + - name: avg_revenue + type: avg + sql: revenue + - name: complex_measure + type: number + sql: "{sum_revenue} + {CUBE.avg_revenue}/{test_cube.min_revenue} - {test_cube.min_revenue}" + - name: count_distinct_id + type: countDistinct + sql: id + - name: count_distinct_approx_id + type: countDistinctApprox + sql: id diff --git a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs index 2e2dfa5bf13ed..9f6a031626629 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs @@ -1,64 +1,9 @@ use crate::test_fixtures::cube_bridge::MockSchema; use crate::test_fixtures::test_utils::TestContext; -use indoc::indoc; - -fn create_ownership_test_schema() -> MockSchema { - let yaml = indoc! {r#" - cubes: - - name: users - sql: "SELECT 1" - joins: - - name: orders - relationship: one_to_many - sql: "{users}.id = {orders.user_id}" - dimensions: - - name: id - type: number - sql: id - primary_key: true - - name: userName - type: string - sql: "{CUBE}.user_name" - - name: userNameProxy - type: string - sql: "{CUBE.user_name}" - - name: secondId - type: number - sql: "{CUBE}.secondId" - - name: complexId - type: number - sql: "{id} % {users.secondId}" - measures: - - name: count - type: count - - name: orders - sql: "SELECT 1" - dimensions: - - name: id - type: number - sql: id - primary_key: true - - name: orderUserName - type: string - sql: "{users.userName}" - measures: - - name: count - type: count - views: - - name: users_to_orders - cubes: - - join_path: users - includes: "*" - - join_path: users.orders - includes: - - orderUserName - "#}; - MockSchema::from_yaml(yaml).unwrap() -} #[test] fn dimensions_ownships() { - let schema = create_ownership_test_schema(); + let schema = MockSchema::from_yaml_file("owned_by_cube/ownership_test.yaml"); let context = TestContext::new(schema).unwrap(); let symbol = context.create_dimension("users.id").unwrap(); assert!(symbol.owned_by_cube()); diff --git a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/symbol_evaluator.rs b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/symbol_evaluator.rs index 5a47266f60f5a..4f075628df207 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/symbol_evaluator.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/symbol_evaluator.rs @@ -2,120 +2,10 @@ use crate::test_fixtures::cube_bridge::MockSchema; use crate::test_fixtures::test_utils::TestContext; -use indoc::indoc; - -fn create_count_schema_no_pk() -> MockSchema { - let yaml = indoc! {r#" - cubes: - - name: users - sql: "SELECT 1" - dimensions: - - name: id - type: number - sql: id - - name: userName - type: string - sql: user_name - measures: - - name: count - type: count - "#}; - MockSchema::from_yaml(yaml).unwrap() -} - -fn create_count_schema_one_pk() -> MockSchema { - let yaml = indoc! {r#" - cubes: - - name: users - sql: "SELECT 1" - dimensions: - - name: id - type: number - sql: id - primary_key: true - - name: userName - type: string - sql: user_name - measures: - - name: count - type: count - "#}; - MockSchema::from_yaml(yaml).unwrap() -} - -fn create_count_schema_two_pk() -> MockSchema { - let yaml = indoc! {r#" - cubes: - - name: users - sql: "SELECT 1" - dimensions: - - name: id - type: number - sql: id - primary_key: true - - name: userName - type: string - sql: user_name - primary_key: true - measures: - - name: count - type: count - "#}; - MockSchema::from_yaml(yaml).unwrap() -} - -fn create_test_schema() -> MockSchema { - let yaml = indoc! {r#" - cubes: - - name: test_cube - sql: "SELECT 1" - dimensions: - - name: id - type: number - sql: id - primary_key: true - - name: source - type: string - sql: "{CUBE}.source" - - name: source_extended - type: string - sql: "CONCAT({CUBE.source}, '_source')" - - name: created_at - type: time - sql: created_at - - name: location - type: geo - latitude: latitude - longitude: longitude - measures: - - name: sum_revenue - type: sum - sql: revenue - - name: min_revenue - type: min - sql: revenue - - name: max_revenue - type: max - sql: revenue - - name: avg_revenue - type: avg - sql: revenue - - name: complex_measure - type: number - sql: "{sum_revenue} + {CUBE.avg_revenue}/{test_cube.min_revenue} - {test_cube.min_revenue}" - - name: count_distinct_id - type: countDistinct - sql: id - - name: count_distinct_approx_id - type: countDistinctApprox - sql: id - "#}; - MockSchema::from_yaml(yaml).unwrap() -} #[test] fn simple_dimension_sql_evaluation() { - let schema = create_test_schema(); + let schema = MockSchema::from_yaml_file("symbol_evaluator/test_cube.yaml"); let context = TestContext::new(schema).unwrap(); let id_symbol = context.create_dimension("test_cube.id").unwrap(); @@ -146,7 +36,7 @@ fn simple_dimension_sql_evaluation() { #[test] fn simple_aggregate_measures() { - let schema = create_test_schema(); + let schema = MockSchema::from_yaml_file("symbol_evaluator/test_cube.yaml"); let context = TestContext::new(schema).unwrap(); let sum_symbol = context.create_measure("test_cube.sum_revenue").unwrap(); @@ -185,13 +75,13 @@ fn simple_aggregate_measures() { #[test] fn count_measure_variants() { - let schema_no_pk = create_count_schema_no_pk(); + let schema_no_pk = MockSchema::from_yaml_file("symbol_evaluator/count_no_pk.yaml"); let context_no_pk = TestContext::new(schema_no_pk).unwrap(); let count_no_pk_symbol = context_no_pk.create_measure("users.count").unwrap(); let count_no_pk_sql = context_no_pk.evaluate_symbol(&count_no_pk_symbol).unwrap(); assert_eq!(count_no_pk_sql, "count(*)"); - let schema_one_pk = create_count_schema_one_pk(); + let schema_one_pk = MockSchema::from_yaml_file("symbol_evaluator/count_one_pk.yaml"); let context_one_pk = TestContext::new(schema_one_pk).unwrap(); let count_one_pk_symbol = context_one_pk.create_measure("users.count").unwrap(); let count_one_pk_sql = context_one_pk @@ -199,7 +89,8 @@ fn count_measure_variants() { .unwrap(); assert_eq!(count_one_pk_sql, r#"count("users".id)"#); - let schema_two_pk = create_count_schema_two_pk(); + // Test COUNT with two primary keys - should use count(CAST(pk1) || CAST(pk2)) + let schema_two_pk = MockSchema::from_yaml_file("symbol_evaluator/count_two_pk.yaml"); let context_two_pk = TestContext::new(schema_two_pk).unwrap(); let count_two_pk_symbol = context_two_pk.create_measure("users.count").unwrap(); let count_two_pk_sql = context_two_pk @@ -213,7 +104,7 @@ fn count_measure_variants() { #[test] fn composite_symbols() { - let schema = create_test_schema(); + let schema = MockSchema::from_yaml_file("symbol_evaluator/test_cube.yaml"); let context = TestContext::new(schema).unwrap(); // Test dimension with member dependency ({CUBE.source}) From e3573d495b8acf2a7df085c54eaa6d42ae389d60 Mon Sep 17 00:00:00 2001 From: Aleksandr Romanenko Date: Thu, 20 Nov 2025 20:56:58 +0100 Subject: [PATCH 05/12] owned_by_cube refactoring --- .../sql_evaluator/symbols/common/case.rs | 29 ++ .../sql_evaluator/symbols/dimension_symbol.rs | 21 ++ .../sql_evaluator/symbols/measure_symbol.rs | 30 +- .../owned_by_cube/ownership_test.yaml | 259 ++++++++++++++---- .../src/tests/cube_evaluator/owned_by_cube.rs | 82 +++++- 5 files changed, 367 insertions(+), 54 deletions(-) diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/common/case.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/common/case.rs index 0542e3a259d4b..a17753dda8b56 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/common/case.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/common/case.rs @@ -77,6 +77,14 @@ impl CaseDefinition { let res = CaseDefinition { items, else_label }; Ok(res) } + + fn is_owned_by_cube(&self) -> bool { + let mut owned = false; + for itm in self.items.iter() { + owned |= itm.sql.is_owned_by_cube(); + } + owned + } } #[derive(Clone)] @@ -164,6 +172,21 @@ impl CaseSwitchDefinition { } values_len == 1 } + + fn is_owned_by_cube(&self) -> bool { + let mut owned = false; + if let CaseSwitchItem::Sql(sql) = &self.switch { + owned |= sql.is_owned_by_cube(); + } + for itm in self.items.iter() { + owned |= itm.sql.is_owned_by_cube(); + } + if let Some(sql) = &self.else_sql { + owned |= sql.is_owned_by_cube(); + } + owned + } + fn extract_symbol_deps(&self, result: &mut Vec>) { self.switch.extract_symbol_deps(result); for itm in self.items.iter() { @@ -370,6 +393,12 @@ impl Case { Case::CaseSwitch(case) => case.is_single_value(), } } + pub fn is_owned_by_cube(&self) -> bool { + match self { + Case::Case(case) => case.is_owned_by_cube(), + Case::CaseSwitch(case) => case.is_owned_by_cube(), + } + } } impl crate::utils::debug::DebugSql for Case { diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/dimension_symbol.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/dimension_symbol.rs index aab65a70e775e..5680634b86464 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/dimension_symbol.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/dimension_symbol.rs @@ -519,10 +519,31 @@ impl SymbolFactory for DimensionSymbolFactory { let is_multi_stage = definition.static_data().multi_stage.unwrap_or(false); +<<<<<<< HEAD //TODO move owned logic to rust let owned_by_cube = definition.static_data().owned_by_cube.unwrap_or(true); let owned_by_cube = owned_by_cube && !is_multi_stage && definition.static_data().dimension_type != "switch"; +======= + let owned_by_cube = if is_multi_stage || dimension_type == "switch" { + false + } else { + let mut owned = false; + if let Some(sql) = &sql { + owned |= sql.is_owned_by_cube(); + } + if let Some(sql) = &latitude { + owned |= sql.is_owned_by_cube(); + } + if let Some(sql) = &longitude { + owned |= sql.is_owned_by_cube(); + } + if let Some(case) = &case { + owned |= case.is_owned_by_cube(); + } + owned + }; +>>>>>>> d8910d3aee (owned_by_cube refactoring) let is_sub_query = definition.static_data().sub_query.unwrap_or(false); let is_reference = (is_view && is_sql_direct_ref) || (!owned_by_cube diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/measure_symbol.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/measure_symbol.rs index 4781ba76acc8c..f47a7b35e3541 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/measure_symbol.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/measure_symbol.rs @@ -746,14 +746,32 @@ impl SymbolFactory for MeasureSymbolFactory { None }; - let is_calculated = - MeasureSymbol::is_calculated_type(&definition.static_data().measure_type) - && !definition.static_data().multi_stage.unwrap_or(false); + let measure_type = &definition.static_data().measure_type; + let is_calculated = MeasureSymbol::is_calculated_type(&measure_type) + && !definition.static_data().multi_stage.unwrap_or(false); let is_multi_stage = definition.static_data().multi_stage.unwrap_or(false); - //TODO move owned logic to rust - let owned_by_cube = definition.static_data().owned_by_cube.unwrap_or(true); - let owned_by_cube = owned_by_cube && !is_multi_stage; + let owned_by_cube = if is_multi_stage { + false + } else if measure_type == "count" && sql.is_none() { + true + } else { + let mut owned = false; + if let Some(sql) = &sql { + owned |= sql.is_owned_by_cube(); + } + for sql in &measure_filters { + owned |= sql.is_owned_by_cube(); + } + for sql in &measure_drill_filters { + owned |= sql.is_owned_by_cube(); + } + if let Some(case) = &case { + owned |= case.is_owned_by_cube(); + } + owned + }; + let cube = cube_evaluator.cube_from_path(cube_name.clone())?; let alias = PlanSqlTemplates::memeber_alias_name(cube.static_data().resolved_alias(), &name, &None); diff --git a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/owned_by_cube/ownership_test.yaml b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/owned_by_cube/ownership_test.yaml index ab2a5120299ad..806496d8fa065 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/owned_by_cube/ownership_test.yaml +++ b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/owned_by_cube/ownership_test.yaml @@ -1,48 +1,215 @@ cubes: - - name: users - sql: "SELECT 1" - joins: - - name: orders - relationship: one_to_many - sql: "{users}.id = {orders.user_id}" - dimensions: - - name: id - type: number - sql: id - primary_key: true - - name: userName - type: string - sql: "{CUBE}.user_name" - - name: userNameProxy - type: string - sql: "{CUBE.user_name}" - - name: secondId - type: number - sql: "{CUBE}.secondId" - - name: complexId - type: number - sql: "{id} % {users.secondId}" - measures: - - name: count - type: count - - name: orders - sql: "SELECT 1" - dimensions: - - name: id - type: number - sql: id - primary_key: true - - name: orderUserName - type: string - sql: "{users.userName}" - measures: - - name: count - type: count + - name: users + sql: "SELECT 1" + joins: + - name: orders + relationship: one_to_many + sql: "{users}.id = {orders.user_id}" + dimensions: + - name: id + type: number + sql: id + primary_key: true + + - name: userName + type: string + sql: "{CUBE}.user_name" + + - name: userType + type: string + sql: "{CUBE}.user_type" + + - name: userNameProxy + type: string + sql: "{CUBE.userName}" + + - name: secondId + type: number + sql: "{CUBE}.secondId" + + - name: complexId + type: number + sql: "{id} % {users.secondId}" + + - name: ownedCase + type: string + case: + when: + - sql: "{CUBE}.user_type = 'admin'" + label: "Admin" + - sql: "{CUBE}.user_type = 'user'" + label: "User" + else: + label: "Unknown" + + - name: anotherOwnedCase + type: string + case: + when: + - sql: "{CUBE}.user_type = 'admin'" + label: "Admin" + - sql: "{orders.orderType} = 'user'" + label: "User" + else: + label: "Unknown" + + - name: notOwnedCase + type: string + case: + when: + - sql: "{CUBE.userType} = 'admin'" + label: "Admin" + - sql: "{orders.orderType} = 'user'" + label: "User" + else: + label: "Unknown" + + - name: notOwnedCaseOtherCube + type: string + case: + when: + - sql: "{orders.orderType} = 'user'" + label: "User" + else: + label: "Unknown" + + - name: latitude + type: number + sql: "{CUBE}.latitude" + + - name: longitude + type: number + sql: "{CUBE}.longitude" + + - name: ownedGeo + type: geo + latitude: latitude + longitude: longitude + + - name: anotherOwnedGeo + type: geo + latitude: "{CUBE.latitude}" + longitude: "{CUBE}.longitude" + + - name: notOwnedGeo + type: geo + latitude: "{CUBE.latitude}" + longitude: "{CUBE.longitude}" + + - name: notOwnedGeoTypeOtherCube + type: geo + latitude: "{CUBE.latitude}" + longitude: "{CUBE.longitude}" + + measures: + - name: count + type: count + + - name: amount + type: sum + sql: "amount" + + - name: minPayment + type: min + sql: "{users}.payment" + + - name: proxyAmount + type: number + sql: "{CUBE.amount}" + + - name: complexCalculation + type: number + sql: "{CUBE.amount} / {CUBE.minPayment}" + + - name: complexCalculationOtherCube + type: number + sql: "{orders.revenue} / {orders.amount}" + + - name: multiStage + type: number + sql: "{CUBE.amount} / {CUBE.minPayment}" + multi_stage: true + + - name: ownedFilter + type: number + sql: "{CUBE.amount} / {CUBE.minPayment}" + filters: + - sql: "{CUBE}.amount > 100" + + - name: otherOwnedFilter + type: number + sql: "{orders.revenue}" + filters: + - sql: "{CUBE}.amount > 100" + + - name: notOwnedFilter + type: number + sql: "{CUBE.amount} / {CUBE.minPayment}" + filters: + - sql: "{CUBE.amount} > 100" + + - name: notOwnedFilterOtherCube + type: number + sql: "{orders.revenue}" + filters: + - sql: "{orders.count} > 100" + + - name: ownedDrillFilter + type: number + sql: "{CUBE.amount} / {CUBE.minPayment}" + drill_filters: + - sql: "{CUBE}.amount > 100" + + - name: otherOwnedDrillFilter + type: number + sql: "{orders.revenue}" + drill_filters: + - sql: "{CUBE}.amount > 100" + + - name: notOwnedDrillFilter + type: number + sql: "{CUBE.amount} / {CUBE.minPayment}" + drill_filters: + - sql: "{CUBE.amount} > 100" + + - name: notOwnedDrillFilterOtherCube + type: number + sql: "{orders.revenue}" + drill_filters: + - sql: "{orders.count} > 100" + + - name: orders + sql: "SELECT 1" + dimensions: + - name: id + type: number + sql: id + primary_key: true + - name: orderUserName + type: string + sql: "{users.userName}" + - name: orderType + type: string + sql: "{CUBE}.order_type" + - name: latitude + type: number + sql: "{CUBE}.latitude" + - name: longitude + type: number + sql: "{CUBE}.longitude" + measures: + - name: count + type: count + + - name: revenue + type: sum + sql: "{CUBE}.revenue" views: - - name: users_to_orders - cubes: - - join_path: users - includes: "*" - - join_path: users.orders - includes: - - orderUserName + - name: users_to_orders + cubes: + - join_path: users + includes: "*" + - join_path: users.orders + includes: + - orderUserName + - orderType diff --git a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs index 9f6a031626629..cedfff2ec263a 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs @@ -1,10 +1,88 @@ use crate::test_fixtures::cube_bridge::MockSchema; use crate::test_fixtures::test_utils::TestContext; +use crate::utils::debug::DebugSql; #[test] fn dimensions_ownships() { let schema = MockSchema::from_yaml_file("owned_by_cube/ownership_test.yaml"); let context = TestContext::new(schema).unwrap(); - let symbol = context.create_dimension("users.id").unwrap(); - assert!(symbol.owned_by_cube()); + + let owned_dims = vec![ + "users.id", + "users.userName", + "users.ownedCase", + "users.anotherOwnedCase", + "users.ownedGeo", + "users.anotherOwnedGeo", + "orders.orderType", + ]; + + for dim in owned_dims { + assert!( + context.create_dimension(dim).unwrap().owned_by_cube(), + "Dimension {} should be owned by cube", + dim + ); + } + + let not_owned_dims = vec![ + "users.userNameProxy", + "users.complexId", + "users.notOwnedCase", + "users.notOwnedCaseOtherCube", + "users.notOwnedGeo", + "users.notOwnedGeoTypeOtherCube", + "users_to_orders.id", + "users_to_orders.orderType", + ]; + + for dim in not_owned_dims { + assert!( + !context.create_dimension(dim).unwrap().owned_by_cube(), + "Dimension {} should not be owned by cube", + dim + ); + } +} + +#[test] +fn measures_ownships() { + let schema = MockSchema::from_yaml_file("owned_by_cube/ownership_test.yaml"); + let context = TestContext::new(schema).unwrap(); + + let owned_measures = vec![ + "users.count", + "users.amount", + "users.minPayment", + "users.ownedFilter", + "users.otherOwnedFilter", + "users.ownedDrillFilter", + "users.otherOwnedDrillFilter", + ]; + + for meas in owned_measures { + assert!( + context.create_measure(meas).unwrap().owned_by_cube(), + "Measure {} should be owned by cube", + meas + ); + } + + let not_owned_measures = vec![ + "users.proxyAmount", + "users.complexCalculation", + "users.notOwnedFilter", + "users.notOwnedFilterOtherCube", + "users.notOwnedDrillFilter", + "users.notOwnedDrillFilterOtherCube", + "users_to_orders.count", + ]; + + for meas in not_owned_measures { + assert!( + !context.create_measure(meas).unwrap().owned_by_cube(), + "Measure {} should not be owned by cube", + meas + ); + } } From d61a19ca206980ea20b884177c39e151d84c56fc Mon Sep 17 00:00:00 2001 From: Aleksandr Romanenko Date: Thu, 27 Nov 2025 18:09:48 +0100 Subject: [PATCH 06/12] in work --- .../src/planner/sql_evaluator/symbols/dimension_symbol.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/dimension_symbol.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/dimension_symbol.rs index 5680634b86464..21727fffb4eb6 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/dimension_symbol.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/dimension_symbol.rs @@ -519,12 +519,6 @@ impl SymbolFactory for DimensionSymbolFactory { let is_multi_stage = definition.static_data().multi_stage.unwrap_or(false); -<<<<<<< HEAD - //TODO move owned logic to rust - let owned_by_cube = definition.static_data().owned_by_cube.unwrap_or(true); - let owned_by_cube = - owned_by_cube && !is_multi_stage && definition.static_data().dimension_type != "switch"; -======= let owned_by_cube = if is_multi_stage || dimension_type == "switch" { false } else { @@ -543,7 +537,6 @@ impl SymbolFactory for DimensionSymbolFactory { } owned }; ->>>>>>> d8910d3aee (owned_by_cube refactoring) let is_sub_query = definition.static_data().sub_query.unwrap_or(false); let is_reference = (is_view && is_sql_direct_ref) || (!owned_by_cube From 4848bef7a1c0ccb351d4c8d4b0ec68e9182376be Mon Sep 17 00:00:00 2001 From: Aleksandr Romanenko Date: Thu, 20 Nov 2025 17:38:08 +0100 Subject: [PATCH 07/12] in work --- .../src/planner/sql_evaluator/sql_call.rs | 28 ++++- .../planner/sql_evaluator/sql_call_builder.rs | 40 ++++-- .../sql_evaluator/symbols/member_symbol.rs | 10 ++ .../src/tests/cube_evaluator/compilation.rs | 116 ++++++++++-------- 4 files changed, 136 insertions(+), 58 deletions(-) diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call.rs index 99b998ed8fb90..ce55faec08819 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call.rs @@ -9,7 +9,6 @@ use cubenativeutils::CubeError; use itertools::Itertools; use std::collections::HashMap; use std::rc::Rc; -use typed_builder::TypedBuilder; pub struct SqlCallArg; @@ -50,7 +49,7 @@ pub struct SqlCallFilterGroupItem { pub filter_params: Vec, } -#[derive(Clone, TypedBuilder, Debug)] +#[derive(Clone, Debug)] pub struct SqlCall { template: SqlTemplate, deps: Vec, @@ -60,6 +59,22 @@ pub struct SqlCall { } impl SqlCall { + pub(super) fn new( + template: SqlTemplate, + deps: Vec, + filter_params: Vec, + filter_groups: Vec, + security_context: SecutityContextProps, + ) -> Self { + Self { + template, + deps, + filter_params, + filter_groups, + security_context, + } + } + pub fn eval( &self, visitor: &SqlEvaluatorVisitor, @@ -71,7 +86,6 @@ impl SqlCall { let (filter_params, filter_groups, deps, context_values) = self.prepare_template_params(visitor, node_processor, &query_tools, templates)?; - // Substitute placeholders in template in a single pass Self::substitute_template( template, &deps, @@ -122,6 +136,14 @@ impl SqlCall { Ok(result) } + pub fn is_owned_by_cube(&self) -> bool { + if self.deps.is_empty() { + true + } else { + self.deps.iter().any(|dep| dep.symbol.is_cube()) + } + } + fn prepare_template_params( &self, visitor: &SqlEvaluatorVisitor, diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call_builder.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call_builder.rs index aea777a0a3220..1c2a9d9af4771 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call_builder.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call_builder.rs @@ -8,6 +8,7 @@ use crate::cube_bridge::security_context::SecurityContext; use crate::planner::sql_evaluator::TimeDimensionSymbol; use crate::planner::GranularityHelper; use cubenativeutils::CubeError; +use itertools::Itertools; use std::rc::Rc; pub struct SqlCallBuilder<'a> { @@ -46,6 +47,8 @@ impl<'a> SqlCallBuilder<'a> { .map(|path| self.build_dependency(cube_name, path)) .collect::, _>>()?; + Self::validate_deps(cube_name, &deps)?; + let filter_params = template_args .filter_params .iter() @@ -58,16 +61,39 @@ impl<'a> SqlCallBuilder<'a> { .map(|itm| self.build_filter_group_item(itm)) .collect::, _>>()?; - let result = SqlCall::builder() - .template(template.clone()) - .deps(deps) - .filter_params(filter_params) - .filter_groups(filter_groups) - .security_context(template_args.security_context.clone()) - .build(); + let result = SqlCall::new( + template.clone(), + deps, + filter_params, + filter_groups, + template_args.security_context.clone(), + ); Ok(result) } + fn validate_deps(cube_name: &String, deps: &Vec) -> Result<(), CubeError> { + let foreign_cubes = deps + .iter() + .filter_map(|dep| { + if let Ok(cube) = dep.symbol.as_cube_name() { + if cube_name != cube.cube_name() { + return Some(cube.cube_name().clone()); + } + } + None + }) + .collect_vec(); + + if !foreign_cubes.is_empty() { + return Err(CubeError::user(format!( + "Member sql in cube {} references to foreign cubes: {}", + cube_name, + foreign_cubes.join(", ") + ))); + } + Ok(()) + } + fn build_filter_params_item( &mut self, item: &FilterParamsItem, diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/member_symbol.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/member_symbol.rs index 6190462fc6ba2..532268b777ba1 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/member_symbol.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/member_symbol.rs @@ -295,6 +295,16 @@ impl MemberSymbol { } } + pub fn as_cube_name(&self) -> Result, CubeError> { + match self { + Self::CubeName(c) => Ok(c.clone()), + _ => Err(CubeError::internal(format!( + "{} is not a cube name", + self.full_name() + ))), + } + } + pub fn as_member_expression(&self) -> Result, CubeError> { match self { Self::MemberExpression(m) => Ok(m.clone()), diff --git a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/compilation.rs b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/compilation.rs index f0cb8b507f209..e7a49b946d9ba 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/compilation.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/compilation.rs @@ -279,15 +279,12 @@ fn test_add_cube_name_evaluator() { assert_eq!(symbol.cube_name(), "visitors"); } -// Tests for dimensions and measures with dependencies - #[test] fn test_dimension_with_cube_table_dependency() { let schema = MockSchema::from_yaml_file("common/visitors.yaml"); let evaluator = schema.create_evaluator(); let mut test_compiler = TestCompiler::new(evaluator); - // visitor_id has dependency on {CUBE.}visitor_id let symbol = test_compiler .compiler .add_dimension_evaluator("visitors.visitor_id".to_string()) @@ -298,7 +295,6 @@ fn test_dimension_with_cube_table_dependency() { assert_eq!(symbol.cube_name(), "visitors"); assert_eq!(symbol.as_dimension().unwrap().dimension_type(), "number"); - // Should have 1 dependency: CubeTable let dependencies = symbol.get_dependencies(); assert_eq!(dependencies.len(), 1, "Should have 1 dependency on CUBE"); @@ -314,7 +310,6 @@ fn test_dimension_with_member_dependency_no_prefix() { let evaluator = schema.create_evaluator(); let mut test_compiler = TestCompiler::new(evaluator); - // visitor_id_twice has dependency on {visitor_id} without cube prefix let symbol = test_compiler .compiler .add_dimension_evaluator("visitors.visitor_id_twice".to_string()) @@ -325,7 +320,6 @@ fn test_dimension_with_member_dependency_no_prefix() { assert_eq!(symbol.cube_name(), "visitors"); assert_eq!(symbol.as_dimension().unwrap().dimension_type(), "number"); - // Should have 1 dependency: visitor_id dimension let dependencies = symbol.get_dependencies(); assert_eq!( dependencies.len(), @@ -345,7 +339,6 @@ fn test_dimension_with_mixed_dependencies() { let evaluator = schema.create_evaluator(); let mut test_compiler = TestCompiler::new(evaluator); - // source_concat_id has dependencies on {CUBE.source} and {visitors.visitor_id} let symbol = test_compiler .compiler .add_dimension_evaluator("visitors.source_concat_id".to_string()) @@ -356,7 +349,6 @@ fn test_dimension_with_mixed_dependencies() { assert_eq!(symbol.cube_name(), "visitors"); assert_eq!(symbol.as_dimension().unwrap().dimension_type(), "string"); - // Should have 2 dependencies: visitors.source and visitors.visitor_id let dependencies = symbol.get_dependencies(); assert_eq!( dependencies.len(), @@ -364,13 +356,11 @@ fn test_dimension_with_mixed_dependencies() { "Should have 2 dimension dependencies" ); - // Both should be dimensions for dep in &dependencies { assert!(dep.is_dimension(), "All dependencies should be dimensions"); assert_eq!(dep.cube_name(), "visitors"); } - // Check we have both expected dependencies let dep_names: Vec = dependencies.iter().map(|d| d.full_name()).collect(); assert!( dep_names.contains(&"visitors.source".to_string()), @@ -388,7 +378,6 @@ fn test_measure_with_cube_table_dependency() { let evaluator = schema.create_evaluator(); let mut test_compiler = TestCompiler::new(evaluator); - // revenue has dependency on {CUBE}.revenue let symbol = test_compiler .compiler .add_measure_evaluator("visitors.revenue".to_string()) @@ -399,7 +388,6 @@ fn test_measure_with_cube_table_dependency() { assert_eq!(symbol.cube_name(), "visitors"); assert_eq!(symbol.as_measure().unwrap().measure_type(), "sum"); - // Should have 1 dependency: CubeTable let dependencies = symbol.get_dependencies(); assert_eq!(dependencies.len(), 1, "Should have 1 dependency on CUBE"); @@ -415,7 +403,6 @@ fn test_measure_with_explicit_cube_and_member_dependencies() { let evaluator = schema.create_evaluator(); let mut test_compiler = TestCompiler::new(evaluator); - // total_revenue_per_count has dependencies on {visitors.count} and {total_revenue} let symbol = test_compiler .compiler .add_measure_evaluator("visitors.total_revenue_per_count".to_string()) @@ -426,17 +413,14 @@ fn test_measure_with_explicit_cube_and_member_dependencies() { assert_eq!(symbol.cube_name(), "visitors"); assert_eq!(symbol.as_measure().unwrap().measure_type(), "number"); - // Should have 2 dependencies: visitors.count and total_revenue let dependencies = symbol.get_dependencies(); assert_eq!(dependencies.len(), 2, "Should have 2 measure dependencies"); - // Both should be measures for dep in &dependencies { assert!(dep.is_measure(), "All dependencies should be measures"); assert_eq!(dep.cube_name(), "visitors"); } - // Check we have both expected dependencies let dep_names: Vec = dependencies.iter().map(|d| d.full_name()).collect(); assert!( dep_names.contains(&"visitors.count".to_string()), @@ -454,29 +438,24 @@ fn test_view_dimension_compilation() { let evaluator = schema.create_evaluator(); let mut test_compiler = TestCompiler::new(evaluator); - // Compile dimension from view with simple join path let id_symbol = test_compiler .compiler .add_dimension_evaluator("visitors_visitors_checkins.id".to_string()) .unwrap(); - // Check basic properties assert!(id_symbol.is_dimension()); assert_eq!(id_symbol.full_name(), "visitors_visitors_checkins.id"); assert_eq!(id_symbol.cube_name(), "visitors_visitors_checkins"); assert_eq!(id_symbol.name(), "id"); - // Check that it's a view member let dimension = id_symbol.as_dimension().unwrap(); assert!(dimension.is_view(), "Should be a view member"); - // Check that it's a reference (view members reference original cube members) assert!( dimension.is_reference(), "Should be a reference to original member" ); - // Resolve reference chain to get the original member let resolved = id_symbol.clone().resolve_reference_chain(); assert_eq!( resolved.full_name(), @@ -488,7 +467,6 @@ fn test_view_dimension_compilation() { "Resolved member should not be a view" ); - // Compile dimension from view with long join path let visitor_id_symbol = test_compiler .compiler .add_dimension_evaluator("visitors_visitors_checkins.visitor_id".to_string()) @@ -504,7 +482,6 @@ fn test_view_dimension_compilation() { assert!(visitor_id_dim.is_view(), "Should be a view member"); assert!(visitor_id_dim.is_reference(), "Should be a reference"); - // Resolve to original member from visitor_checkins cube let resolved = visitor_id_symbol.clone().resolve_reference_chain(); assert_eq!( resolved.full_name(), @@ -523,29 +500,24 @@ fn test_view_measure_compilation() { let evaluator = schema.create_evaluator(); let mut test_compiler = TestCompiler::new(evaluator); - // Compile measure from view with long join path let count_symbol = test_compiler .compiler .add_measure_evaluator("visitors_visitors_checkins.count".to_string()) .unwrap(); - // Check basic properties assert!(count_symbol.is_measure()); assert_eq!(count_symbol.full_name(), "visitors_visitors_checkins.count"); assert_eq!(count_symbol.cube_name(), "visitors_visitors_checkins"); assert_eq!(count_symbol.name(), "count"); - // Check that it's a view member let measure = count_symbol.as_measure().unwrap(); assert!(measure.is_view(), "Should be a view member"); - // Check that it's a reference assert!( measure.is_reference(), "Should be a reference to original member" ); - // Resolve reference chain to get the original member let resolved = count_symbol.clone().resolve_reference_chain(); assert_eq!( resolved.full_name(), @@ -564,13 +536,11 @@ fn test_proxy_dimension_compilation() { let evaluator = schema.create_evaluator(); let mut test_compiler = TestCompiler::new(evaluator); - // Compile proxy dimension that references another dimension let proxy_symbol = test_compiler .compiler .add_dimension_evaluator("visitors.visitor_id_proxy".to_string()) .unwrap(); - // Check basic properties assert!(proxy_symbol.is_dimension()); assert_eq!(proxy_symbol.full_name(), "visitors.visitor_id_proxy"); assert_eq!(proxy_symbol.cube_name(), "visitors"); @@ -578,16 +548,13 @@ fn test_proxy_dimension_compilation() { let dimension = proxy_symbol.as_dimension().unwrap(); - // Check that it's NOT a view member assert!(!dimension.is_view(), "Proxy should not be a view member"); - // Check that it IS a reference (proxy references another member) assert!( dimension.is_reference(), "Proxy should be a reference to another member" ); - // Resolve reference chain to get the target member let resolved = proxy_symbol.clone().resolve_reference_chain(); assert_eq!( resolved.full_name(), @@ -595,13 +562,11 @@ fn test_proxy_dimension_compilation() { "Proxy should resolve to visitors.visitor_id" ); - // Verify the resolved member is not a view assert!( !resolved.as_dimension().unwrap().is_view(), "Target member should not be a view" ); - // Verify the resolved member is also not a reference (it's the actual dimension) assert!( !resolved.as_dimension().unwrap().is_reference(), "Target member should not be a reference" @@ -614,13 +579,11 @@ fn test_proxy_measure_compilation() { let evaluator = schema.create_evaluator(); let mut test_compiler = TestCompiler::new(evaluator); - // Compile proxy measure that references another measure let proxy_symbol = test_compiler .compiler .add_measure_evaluator("visitors.total_revenue_proxy".to_string()) .unwrap(); - // Check basic properties assert!(proxy_symbol.is_measure()); assert_eq!(proxy_symbol.full_name(), "visitors.total_revenue_proxy"); assert_eq!(proxy_symbol.cube_name(), "visitors"); @@ -632,13 +595,11 @@ fn test_proxy_measure_compilation() { assert!(!measure.is_view(), "Proxy should not be a view member"); - // Check that it IS a reference (proxy references another member) assert!( measure.is_reference(), "Proxy should be a reference to another member" ); - // Resolve reference chain to get the target member let resolved = proxy_symbol.clone().resolve_reference_chain(); assert_eq!( resolved.full_name(), @@ -646,13 +607,11 @@ fn test_proxy_measure_compilation() { "Proxy should resolve to visitors.total_revenue" ); - // Verify the resolved member is not a view assert!( !resolved.as_measure().unwrap().is_view(), "Target member should not be a view" ); - // Verify the resolved member is not a reference (it's the actual measure) assert!( !resolved.as_measure().unwrap().is_reference(), "Target member should not be a reference" @@ -665,19 +624,16 @@ fn test_time_dimension_with_granularity_compilation() { let evaluator = schema.create_evaluator(); let mut test_compiler = TestCompiler::new(evaluator); - // Compile time dimension with month granularity let time_symbol = test_compiler .compiler .add_dimension_evaluator("visitors.created_at.month".to_string()) .unwrap(); - // Check that it's a time dimension, not a regular dimension assert!( time_symbol.as_time_dimension().is_ok(), "Should be a time dimension" ); - // Check full name includes granularity assert_eq!( time_symbol.full_name(), "visitors.created_at_month", @@ -686,23 +642,19 @@ fn test_time_dimension_with_granularity_compilation() { assert_eq!(time_symbol.cube_name(), "visitors"); assert_eq!(time_symbol.name(), "created_at"); - // Get as time dimension to check specific properties let time_dim = time_symbol.as_time_dimension().unwrap(); - // Check granularity assert_eq!( time_dim.granularity(), &Some("month".to_string()), "Granularity should be month" ); - // Check that it's NOT a reference assert!( !time_dim.is_reference(), "Time dimension with granularity should not be a reference" ); - // Check base symbol - should be the original dimension without granularity let base_symbol = time_dim.base_symbol(); assert!( base_symbol.is_dimension(), @@ -719,3 +671,71 @@ fn test_time_dimension_with_granularity_compilation() { "Base dimension should be time type" ); } + +#[test] +fn test_sql_deps_validation() { + let evaluator = create_visitors_schema().create_evaluator(); + let mut test_compiler = TestCompiler::new(evaluator); + + let time_symbol = test_compiler + .compiler + .add_dimension_evaluator("visitors.created_at.month".to_string()) + .unwrap(); + + assert!( + time_symbol.as_time_dimension().is_ok(), + "Should be a time dimension" + ); + + assert_eq!( + time_symbol.full_name(), + "visitors.created_at_month", + "Full name should be visitors.created_at_month" + ); + assert_eq!(time_symbol.cube_name(), "visitors"); + assert_eq!(time_symbol.name(), "created_at"); + + let time_dim = time_symbol.as_time_dimension().unwrap(); + + assert_eq!( + time_dim.granularity(), + &Some("month".to_string()), + "Granularity should be month" + ); + + assert!( + !time_dim.is_reference(), + "Time dimension with granularity should not be a reference" + ); + + let base_symbol = time_dim.base_symbol(); + assert!( + base_symbol.is_dimension(), + "Base symbol should be a dimension" + ); + assert_eq!( + base_symbol.full_name(), + "visitors.created_at", + "Base symbol should be visitors.created_at" + ); + assert_eq!( + base_symbol.as_dimension().unwrap().dimension_type(), + "time", + "Base dimension should be time type" + ); +} + +#[test] +fn test_sql_deps_validation_with_reference() { + let evaluator = create_visitors_schema().create_evaluator(); + let mut test_compiler = TestCompiler::new(evaluator); + + assert!(test_compiler + .compiler + .add_dimension_evaluator("visitors.wrong_foreign_cube_ref".to_string()) + .is_err()); + assert!(test_compiler + .compiler + .add_dimension_evaluator("visitors.wrong_double_cube_ref".to_string()) + .is_err()); +} From 44dedf16dedb3ffa2fa2874a0b4a83e8ed948bc5 Mon Sep 17 00:00:00 2001 From: Aleksandr Romanenko Date: Thu, 27 Nov 2025 18:40:27 +0100 Subject: [PATCH 08/12] in work --- .../cubesqlplanner/src/tests/cube_evaluator/compilation.rs | 6 ++++-- .../src/tests/cube_evaluator/owned_by_cube.rs | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/compilation.rs b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/compilation.rs index e7a49b946d9ba..f511ca2b00c90 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/compilation.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/compilation.rs @@ -674,7 +674,8 @@ fn test_time_dimension_with_granularity_compilation() { #[test] fn test_sql_deps_validation() { - let evaluator = create_visitors_schema().create_evaluator(); + let schema = MockSchema::from_yaml_file("common/visitors.yaml"); + let evaluator = schema.create_evaluator(); let mut test_compiler = TestCompiler::new(evaluator); let time_symbol = test_compiler @@ -727,7 +728,8 @@ fn test_sql_deps_validation() { #[test] fn test_sql_deps_validation_with_reference() { - let evaluator = create_visitors_schema().create_evaluator(); + let schema = MockSchema::from_yaml_file("common/visitors.yaml"); + let evaluator = schema.create_evaluator(); let mut test_compiler = TestCompiler::new(evaluator); assert!(test_compiler diff --git a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs index cedfff2ec263a..da6b9092cc2a6 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs @@ -1,6 +1,5 @@ use crate::test_fixtures::cube_bridge::MockSchema; use crate::test_fixtures::test_utils::TestContext; -use crate::utils::debug::DebugSql; #[test] fn dimensions_ownships() { From dfb9f1b69e579dc04dbdb5ef0a16f7b59c9c67c0 Mon Sep 17 00:00:00 2001 From: Aleksandr Romanenko Date: Fri, 21 Nov 2025 17:32:26 +0100 Subject: [PATCH 09/12] in work --- .../src/compiler/CubeEvaluator.ts | 1 + rust/cubesqlplanner/Cargo.lock | 4 +- .../src/planner/sql_evaluator/compiler.rs | 1 + .../src/planner/sql_evaluator/sql_call.rs | 14 ++ .../planner/sql_evaluator/sql_call_builder.rs | 25 +-- .../sql_evaluator/symbols/common/case.rs | 33 +++- .../sql_evaluator/symbols/dimension_symbol.rs | 16 +- .../sql_evaluator/symbols/member_symbol.rs | 57 +++++- .../ownership_test.yaml | 0 .../wrong_cube_refs_test.yaml | 173 ++++++++++++++++++ .../src/tests/cube_evaluator/compilation.rs | 34 ++-- .../src/tests/cube_evaluator/owned_by_cube.rs | 4 +- 12 files changed, 312 insertions(+), 50 deletions(-) rename rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/{owned_by_cube => compilation_tests}/ownership_test.yaml (100%) create mode 100644 rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/compilation_tests/wrong_cube_refs_test.yaml diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts index 24c298f91947a..940890927e309 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts @@ -800,6 +800,7 @@ export class CubeEvaluator extends CubeSymbols { const member = typeMembers[cubeAndName[1]]; if (member === undefined) { + console.log("!!! ", type); throw new UserError(`'${cubeAndName[1]}' not found for path '${path}'`); } diff --git a/rust/cubesqlplanner/Cargo.lock b/rust/cubesqlplanner/Cargo.lock index afb1181ac98d5..b32b048a4c5c7 100644 --- a/rust/cubesqlplanner/Cargo.lock +++ b/rust/cubesqlplanner/Cargo.lock @@ -314,9 +314,9 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encode_unicode" diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/compiler.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/compiler.rs index e9f23022f765a..8a0bfa5dcc4dd 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/compiler.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/compiler.rs @@ -154,6 +154,7 @@ impl Compiler { factory: T, ) -> Result, CubeError> { let node = factory.build(self)?; + node.validate()?; let key = (T::symbol_name().to_string(), full_name.clone()); if T::is_cachable() { self.members.insert(key, node.clone()); diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call.rs index ce55faec08819..574192490272d 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call.rs @@ -3,6 +3,7 @@ use super::{symbols::MemberSymbol, SqlEvaluatorVisitor}; use crate::cube_bridge::member_sql::{FilterParamsColumn, SecutityContextProps, SqlTemplate}; use crate::planner::query_tools::QueryTools; use crate::planner::sql_evaluator::sql_nodes::SqlNodesFactory; +use crate::planner::sql_evaluator::CubeNameSymbol; use crate::planner::sql_templates::PlanSqlTemplates; use crate::planner::VisitorContext; use cubenativeutils::CubeError; @@ -144,6 +145,19 @@ impl SqlCall { } } + pub fn cube_name_deps(&self) -> Vec> { + self.deps + .iter() + .filter_map(|dep| { + if let Ok(cube) = dep.symbol.as_cube_name() { + Some(cube.clone()) + } else { + None + } + }) + .collect() + } + fn prepare_template_params( &self, visitor: &SqlEvaluatorVisitor, diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call_builder.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call_builder.rs index 1c2a9d9af4771..e1105e26ee3d6 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call_builder.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call_builder.rs @@ -47,7 +47,7 @@ impl<'a> SqlCallBuilder<'a> { .map(|path| self.build_dependency(cube_name, path)) .collect::, _>>()?; - Self::validate_deps(cube_name, &deps)?; + //Self::validate_deps(cube_name, &deps)?; let filter_params = template_args .filter_params @@ -71,29 +71,6 @@ impl<'a> SqlCallBuilder<'a> { Ok(result) } - fn validate_deps(cube_name: &String, deps: &Vec) -> Result<(), CubeError> { - let foreign_cubes = deps - .iter() - .filter_map(|dep| { - if let Ok(cube) = dep.symbol.as_cube_name() { - if cube_name != cube.cube_name() { - return Some(cube.cube_name().clone()); - } - } - None - }) - .collect_vec(); - - if !foreign_cubes.is_empty() { - return Err(CubeError::user(format!( - "Member sql in cube {} references to foreign cubes: {}", - cube_name, - foreign_cubes.join(", ") - ))); - } - Ok(()) - } - fn build_filter_params_item( &mut self, item: &FilterParamsItem, diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/common/case.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/common/case.rs index a17753dda8b56..9123e7612e0fd 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/common/case.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/common/case.rs @@ -52,7 +52,7 @@ impl CaseDefinition { } } - pub fn apply_to_deps) -> Result, CubeError>>( + fn apply_to_deps) -> Result, CubeError>>( &self, f: &F, ) -> Result { @@ -78,6 +78,10 @@ impl CaseDefinition { Ok(res) } + fn iter_sql_calls(&self) -> Box> + '_> { + Box::new(self.items.iter().map(|item| &item.sql)) + } + fn is_owned_by_cube(&self) -> bool { let mut owned = false; for itm in self.items.iter() { @@ -114,7 +118,7 @@ impl CaseSwitchItem { } } - pub fn apply_to_deps) -> Result, CubeError>>( + fn apply_to_deps) -> Result, CubeError>>( &self, f: &F, ) -> Result { @@ -124,6 +128,13 @@ impl CaseSwitchItem { }; Ok(res) } + + fn iter_sql_calls(&self) -> Box> + '_> { + match self { + CaseSwitchItem::Sql(sql_call) => Box::new(std::iter::once(sql_call)), + CaseSwitchItem::Member(_) => Box::new(std::iter::empty()), + } + } } #[derive(Clone)] @@ -173,6 +184,17 @@ impl CaseSwitchDefinition { values_len == 1 } + fn iter_sql_calls(&self) -> Box> + '_> { + let result = self + .switch + .iter_sql_calls() + .chain(self.items.iter().map(|item| &item.sql)); + if let Some(else_sql) = &self.else_sql { + Box::new(result.chain(std::iter::once(else_sql))) + } else { + Box::new(result) + } + } fn is_owned_by_cube(&self) -> bool { let mut owned = false; if let CaseSwitchItem::Sql(sql) = &self.switch { @@ -393,6 +415,13 @@ impl Case { Case::CaseSwitch(case) => case.is_single_value(), } } + + pub fn iter_sql_calls(&self) -> Box> + '_> { + match self { + Case::Case(case) => Box::new(case.iter_sql_calls()), + Case::CaseSwitch(case) => Box::new(case.iter_sql_calls()), + } + } pub fn is_owned_by_cube(&self) -> bool { match self { Case::Case(case) => case.is_owned_by_cube(), diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/dimension_symbol.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/dimension_symbol.rs index 21727fffb4eb6..d7a49389fc4f1 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/dimension_symbol.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/dimension_symbol.rs @@ -227,6 +227,16 @@ impl DimensionSymbol { Ok(MemberSymbol::new_dimension(Rc::new(result))) } + pub fn iter_sql_calls(&self) -> Box> + '_> { + let result = self + .member_sql + .iter() + .chain(self.latitude.iter()) + .chain(self.longitude.iter()) + .chain(self.case.iter().flat_map(|case| case.iter_sql_calls())); + Box::new(result) + } + pub fn get_dependencies(&self) -> Vec> { let mut deps = vec![]; if let Some(member_sql) = &self.member_sql { @@ -517,6 +527,7 @@ impl SymbolFactory for DimensionSymbolFactory { None }; + let is_sub_query = definition.static_data().sub_query.unwrap_or(false); let is_multi_stage = definition.static_data().multi_stage.unwrap_or(false); let owned_by_cube = if is_multi_stage || dimension_type == "switch" { @@ -537,7 +548,6 @@ impl SymbolFactory for DimensionSymbolFactory { } owned }; - let is_sub_query = definition.static_data().sub_query.unwrap_or(false); let is_reference = (is_view && is_sql_direct_ref) || (!owned_by_cube && !is_sub_query @@ -609,12 +619,10 @@ impl SymbolFactory for DimensionSymbolFactory { impl crate::utils::debug::DebugSql for DimensionSymbol { fn debug_sql(&self, expand_deps: bool) -> String { - // Handle case expressions if let Some(case) = &self.case { return case.debug_sql(expand_deps); } - // Handle geo dimensions (latitude/longitude pair) if self.dimension_type == "geo" { let lat = self .latitude @@ -629,12 +637,10 @@ impl crate::utils::debug::DebugSql for DimensionSymbol { return format!("GEO({}, {})", lat, lon); } - // Handle switch type dimensions without SQL if self.dimension_type == "switch" && self.member_sql.is_none() { return format!("SWITCH({})", self.full_name()); } - // Standard dimension SQL let res = if let Some(sql) = &self.member_sql { sql.debug_sql(expand_deps) } else { diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/member_symbol.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/member_symbol.rs index 532268b777ba1..59be108e6212c 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/member_symbol.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/member_symbol.rs @@ -1,7 +1,8 @@ use cubenativeutils::CubeError; +use itertools::Itertools; use crate::planner::sql_evaluator::collectors::has_multi_stage_members; -use crate::planner::sql_evaluator::Case; +use crate::planner::sql_evaluator::{Case, SqlCall}; use super::{ CubeNameSymbol, CubeTableSymbol, DimensionSymbol, MeasureSymbol, MemberExpressionSymbol, @@ -333,6 +334,60 @@ impl MemberSymbol { pub fn is_leaf(&self) -> bool { self.get_dependencies().is_empty() } + + pub fn validate(&self) -> Result<(), CubeError> { + self.validate_cube_refs() + } + fn validate_cube_refs(&self) -> Result<(), CubeError> { + let sql_calls = match self { + Self::Dimension(dim) => dim.iter_sql_calls(), + _ => Box::new(std::iter::empty()), + }; + if self.is_multi_stage() { + for call in sql_calls { + self.validate_multi_stage_cube_refs(call)?; + } + } else { + for call in sql_calls { + self.validate_regular_member_cube_refs(call)?; + } + } + Ok(()) + } + fn validate_multi_stage_cube_refs(&self, sql_call: &Rc) -> Result<(), CubeError> { + let cube_name = self.cube_name(); + let sql_cube_deps = sql_call.cube_name_deps(); + if !sql_cube_deps.is_empty() { + Err(CubeError::user(format!( + "Multi stage member '{}' references cubes {}. Multi stage members can only reference other members.", + cube_name, sql_cube_deps.iter().map(|dep| dep.cube_name()).join(", ") + ))) + } else { + Ok(()) + } + } + fn validate_regular_member_cube_refs(&self, sql_call: &Rc) -> Result<(), CubeError> { + let cube_name = self.cube_name(); + let sql_cube_deps = sql_call.cube_name_deps(); + if sql_cube_deps + .iter() + .any(|dep| dep.cube_name() != &cube_name) + { + Err(CubeError::user(format!( + "Member '{}' references foreign cubes: {}. Please split and move this definition to corresponding cubes.", + cube_name, sql_cube_deps.iter().filter_map(|dep| + if dep.cube_name() != &cube_name { + Some(dep.cube_name()) + } else { + None + } + + ).join(", ") + ))) + } else { + Ok(()) + } + } } impl crate::utils::debug::DebugSql for MemberSymbol { diff --git a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/owned_by_cube/ownership_test.yaml b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/compilation_tests/ownership_test.yaml similarity index 100% rename from rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/owned_by_cube/ownership_test.yaml rename to rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/compilation_tests/ownership_test.yaml diff --git a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/compilation_tests/wrong_cube_refs_test.yaml b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/compilation_tests/wrong_cube_refs_test.yaml new file mode 100644 index 0000000000000..bf6a49b843802 --- /dev/null +++ b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/compilation_tests/wrong_cube_refs_test.yaml @@ -0,0 +1,173 @@ +cubes: + - name: users + sql: "SELECT 1" + joins: + - name: orders + relationship: one_to_many + sql: "{users}.id = {orders.user_id}" + dimensions: + - name: id + type: number + sql: id + primary_key: true + + - name: otherCubeRefInSql + type: string + sql: "{orders}.user_name" + + - name: otherCubeRefInSql2 + type: string + sql: "{CUBE}.val + {orders}.user_name" + + - name: otherCubeRefInLongitude + type: geo + longitude: "{orders}.longitude" + latitude: "{CUBE}.latitude" + + - name: otherCubeRefInLatitude + type: geo + longitude: "{CUBE}.longitude" + latitude: "{orders}.latitude" + + - name: otherCubeRefInCaseItem + type: string + case: + when: + - sql: "{CUBE}.id = 1" + label: 1 + - sql: "{orders}.id = 1" + label: 2 + else: + label: 3 + + - name: otherCubeRefInCaseSwitchSwitch + type: string + case: + switch: "{orders}.state" + when: + - value: "a" + sql: "{CUBE}.a" + - value: "b" + sql: "{CUBE}.b" + else: + sql: "{CUBE}.c" + + - name: otherCubeRefInCaseSwitchItem + type: string + case: + switch: "{CUBE}.state" + when: + - value: "a" + sql: "{orders}.a" + - value: "b" + sql: "{CUBE}.b" + else: + sql: "{CUBE}.c" + + - name: otherCubeRefInCaseSwitchElse + type: string + case: + switch: "{CUBE}.state" + when: + - value: "a" + sql: "{CUBE}.a" + - value: "b" + sql: "{CUBE}.b" + else: + sql: "{orders}.c" + + - name: multiStageCubeRefInSql + type: string + multi_stage: true + sql: "{CUBE}.id" + + - name: multiStageOtherCubeRefInSql + type: string + multi_stage: true + sql: "{orders}.id" + + - name: multiStageCubeRefInLongitude + type: geo + longitude: "{CUBE}.longitude" + latitude: "{CUBE.id}" + + - name: multiStageCubeRefInLatitude + type: geo + longitude: "{CUBE.id}" + latitude: "{CUBE}.latitude" + + - name: multiStageCubeRefInCaseItem + type: string + case: + when: + - sql: "{CUBE}.id = 1" + label: 1 + - sql: "{orders}.id = 1" + label: 2 + else: + label: 3 + + - name: multiStageRefInCaseSwitchSwitch + type: string + case: + switch: "{CUBE}.state" + when: + - value: "a" + sql: "{CUBE.id}" + - value: "b" + sql: "{CUBE.id}" + else: + sql: "{orders.id}" + + - name: multiStageRefInCaseSwitchItem + type: string + case: + switch: "{CUBE.id}" + when: + - value: "a" + sql: "{CUBE}.a" + - value: "b" + sql: "{CUBE.id}" + else: + sql: "{orders.id}" + + - name: multiStageRefInCaseSwitchElse + type: string + case: + switch: "{CUBE.id}" + when: + - value: "a" + sql: "{CUBE.id}" + - value: "b" + sql: "{orders.id}" + else: + sql: "{CUBE}.c" + + measures: + + - name: orders + sql: "SELECT 1" + dimensions: + - name: id + type: number + sql: id + primary_key: true + - name: orderUserName + type: string + sql: "{users.userName}" + - name: orderType + type: string + sql: "{CUBE}.order_type" + - name: latitude + type: number + sql: "{CUBE}.latitude" + - name: longitude + type: number + sql: "{CUBE}.longitude" + measures: + - name: count + type: count + + - name: revenue + type: sum + sql: "{CUBE}.revenue" diff --git a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/compilation.rs b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/compilation.rs index f511ca2b00c90..71cf382e16645 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/compilation.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/compilation.rs @@ -1,6 +1,8 @@ //! Tests for Compiler member evaluation -use crate::test_fixtures::{cube_bridge::MockSchema, schemas::TestCompiler}; +use crate::test_fixtures::cube_bridge::MockSchema; +use crate::test_fixtures::schemas::TestCompiler; +use crate::test_fixtures::test_utils::TestContext; #[test] fn test_add_dimension_evaluator_number_dimension() { @@ -727,17 +729,21 @@ fn test_sql_deps_validation() { } #[test] -fn test_sql_deps_validation_with_reference() { - let schema = MockSchema::from_yaml_file("common/visitors.yaml"); - let evaluator = schema.create_evaluator(); - let mut test_compiler = TestCompiler::new(evaluator); - - assert!(test_compiler - .compiler - .add_dimension_evaluator("visitors.wrong_foreign_cube_ref".to_string()) - .is_err()); - assert!(test_compiler - .compiler - .add_dimension_evaluator("visitors.wrong_double_cube_ref".to_string()) - .is_err()); +fn test_sql_regular_dimension_wrong_cube_ref() { + let schema = MockSchema::from_yaml_file("compilation_tests/wrong_cube_refs_test.yaml"); + let context = TestContext::new(schema).unwrap(); + let wron_dims = vec![ + "users.otherCubeRefInSql", + "users.otherCubeRefInSql2", + "users.otherCubeRefInLongitude", + "users.otherCubeRefInLatitude", + "users.otherCubeRefInCaseItem", + "users.otherCubeRefInCaseSwitchSwitch", + "users.otherCubeRefInCaseSwitchItem", + "users.otherCubeRefInCaseSwitchElse", + ]; + + for dim in wron_dims { + assert!(context.create_dimension(dim).is_err()); + } } diff --git a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs index da6b9092cc2a6..8a64781523a62 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/owned_by_cube.rs @@ -3,7 +3,7 @@ use crate::test_fixtures::test_utils::TestContext; #[test] fn dimensions_ownships() { - let schema = MockSchema::from_yaml_file("owned_by_cube/ownership_test.yaml"); + let schema = MockSchema::from_yaml_file("compilation_tests/ownership_test.yaml"); let context = TestContext::new(schema).unwrap(); let owned_dims = vec![ @@ -46,7 +46,7 @@ fn dimensions_ownships() { #[test] fn measures_ownships() { - let schema = MockSchema::from_yaml_file("owned_by_cube/ownership_test.yaml"); + let schema = MockSchema::from_yaml_file("compilation_tests/ownership_test.yaml"); let context = TestContext::new(schema).unwrap(); let owned_measures = vec![ From 42af22e3ac6b33cf29dfc7378b50a1c475382b91 Mon Sep 17 00:00:00 2001 From: Aleksandr Romanenko Date: Fri, 21 Nov 2025 18:41:43 +0100 Subject: [PATCH 10/12] measure validation --- .../sql_evaluator/symbols/measure_symbol.rs | 10 ++ .../sql_evaluator/symbols/member_symbol.rs | 11 +- .../wrong_cube_refs_test.yaml | 145 ++++++++++++++++-- .../src/tests/cube_evaluator/compilation.rs | 74 ++++++++- 4 files changed, 227 insertions(+), 13 deletions(-) diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/measure_symbol.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/measure_symbol.rs index f47a7b35e3541..5530b3d082964 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/measure_symbol.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/measure_symbol.rs @@ -358,6 +358,16 @@ impl MeasureSymbol { Ok(MemberSymbol::new_measure(Rc::new(result))) } + 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 + let result = self + .member_sql + .iter() + .chain(self.case.iter().flat_map(|case| case.iter_sql_calls())); + Box::new(result) + } + pub fn get_dependencies(&self) -> Vec> { let mut deps = vec![]; if let Some(member_sql) = &self.member_sql { diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/member_symbol.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/member_symbol.rs index 59be108e6212c..05e2c037f8be3 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/member_symbol.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/member_symbol.rs @@ -341,6 +341,7 @@ impl MemberSymbol { fn validate_cube_refs(&self) -> Result<(), CubeError> { let sql_calls = match self { Self::Dimension(dim) => dim.iter_sql_calls(), + Self::Measure(meas) => meas.iter_sql_calls(), _ => Box::new(std::iter::empty()), }; if self.is_multi_stage() { @@ -355,12 +356,16 @@ impl MemberSymbol { Ok(()) } fn validate_multi_stage_cube_refs(&self, sql_call: &Rc) -> Result<(), CubeError> { - let cube_name = self.cube_name(); let sql_cube_deps = sql_call.cube_name_deps(); if !sql_cube_deps.is_empty() { Err(CubeError::user(format!( "Multi stage member '{}' references cubes {}. Multi stage members can only reference other members.", - cube_name, sql_cube_deps.iter().map(|dep| dep.cube_name()).join(", ") + self.full_name(), sql_cube_deps.iter().map(|dep| dep.cube_name()).join(", ") + ))) + } else if sql_call.dependencies_count() == 0 { + Err(CubeError::user(format!( + "Multi stage member '{}' don't reference other members.", + self.full_name() ))) } else { Ok(()) @@ -375,7 +380,7 @@ impl MemberSymbol { { Err(CubeError::user(format!( "Member '{}' references foreign cubes: {}. Please split and move this definition to corresponding cubes.", - cube_name, sql_cube_deps.iter().filter_map(|dep| + self.full_name(), sql_cube_deps.iter().filter_map(|dep| if dep.cube_name() != &cube_name { Some(dep.cube_name()) } else { diff --git a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/compilation_tests/wrong_cube_refs_test.yaml b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/compilation_tests/wrong_cube_refs_test.yaml index bf6a49b843802..69c700870683c 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/compilation_tests/wrong_cube_refs_test.yaml +++ b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/compilation_tests/wrong_cube_refs_test.yaml @@ -11,6 +11,7 @@ cubes: sql: id primary_key: true + # Regular dimensions refers to foreign cubes - name: otherCubeRefInSql type: string sql: "{orders}.user_name" @@ -34,11 +35,11 @@ cubes: case: when: - sql: "{CUBE}.id = 1" - label: 1 + label: a - sql: "{orders}.id = 1" - label: 2 + label: b else: - label: 3 + label: c - name: otherCubeRefInCaseSwitchSwitch type: string @@ -76,6 +77,7 @@ cubes: else: sql: "{orders}.c" + # MultiStage dimensions refers any cubes - name: multiStageCubeRefInSql type: string multi_stage: true @@ -90,25 +92,29 @@ cubes: type: geo longitude: "{CUBE}.longitude" latitude: "{CUBE.id}" + multi_stage: true - name: multiStageCubeRefInLatitude type: geo longitude: "{CUBE.id}" latitude: "{CUBE}.latitude" + multi_stage: true - name: multiStageCubeRefInCaseItem type: string + multi_stage: true case: when: - sql: "{CUBE}.id = 1" - label: 1 + label: a - sql: "{orders}.id = 1" - label: 2 + label: b else: - label: 3 + label: c - - name: multiStageRefInCaseSwitchSwitch + - name: multiStageCubeRefInCaseSwitchSwitch type: string + multi_stage: true case: switch: "{CUBE}.state" when: @@ -119,8 +125,9 @@ cubes: else: sql: "{orders.id}" - - name: multiStageRefInCaseSwitchItem + - name: multiStageCubeRefInCaseSwitchItem type: string + multi_stage: true case: switch: "{CUBE.id}" when: @@ -131,8 +138,9 @@ cubes: else: sql: "{orders.id}" - - name: multiStageRefInCaseSwitchElse + - name: multiStageCubeRefInCaseSwitchElse type: string + multi_stage: true case: switch: "{CUBE.id}" when: @@ -143,7 +151,126 @@ cubes: else: sql: "{CUBE}.c" + # MultiStage dimensions withiout references to other members + - name: multiStageWithoutRefInSql + type: string + multi_stage: true + sql: "id" + + - name: multiStageWithoutRefInLongitude + type: geo + longitude: "longitude" + latitude: "{CUBE.id}" + multi_stage: true + + - name: multiStageWithoutRefInLatitude + type: geo + longitude: "{CUBE.id}" + latitude: "latitude" + multi_stage: true + + - name: multiStageWithoutRefInCaseItem + type: string + multi_stage: true + case: + when: + - sql: "{CUBE.id} = 1" + label: a + - sql: "id = 1" + label: b + else: + label: c + + - name: multiStageWithoutRefInCaseSwitchSwitch + type: string + multi_stage: true + case: + switch: "state" + when: + - value: "a" + sql: "{CUBE.id}" + - value: "b" + sql: "{CUBE.id}" + else: + sql: "{orders.id}" + + - name: multiStageWithoutRefInCaseSwitchItem + type: string + multi_stage: true + case: + switch: "{CUBE.id}" + when: + - value: "a" + sql: "a" + - value: "b" + sql: "{CUBE.id}" + else: + sql: "{orders.id}" + + - name: multiStageWithoutRefInCaseSwitchElse + type: string + multi_stage: true + case: + switch: "{CUBE.id}" + when: + - value: "a" + sql: "{CUBE.id}" + - value: "b" + sql: "{orders.id}" + else: + sql: "c" + measures: + - name: count + type: count + + - name: mOtherCubeRefInSql + type: string + sql: "{orders}.count" + + - name: mMultiStageCubeRefInSql + type: string + multi_stage: true + sql: "{CUBE}.count" + + - name: mMultiStageWithoutRefInCaseSwitchSwitch + type: string + multi_stage: true + case: + switch: "state" + when: + - value: "a" + sql: "{CUBE.count}" + - value: "b" + sql: "{CUBE.count}" + else: + sql: "{orders.count}" + + - name: mMultiStageWithoutRefInCaseSwitchItem + type: string + multi_stage: true + case: + switch: "{CUBE.id}" + when: + - value: "a" + sql: "a" + - value: "b" + sql: "{CUBE.count}" + else: + sql: "{orders.count}" + + - name: mMultiStageWithoutRefInCaseSwitchElse + type: string + multi_stage: true + case: + switch: "{CUBE.count}" + when: + - value: "a" + sql: "{CUBE.count}" + - value: "b" + sql: "{orders.count}" + else: + sql: "c" - name: orders sql: "SELECT 1" diff --git a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/compilation.rs b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/compilation.rs index 71cf382e16645..dadad8e0ad8b1 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/compilation.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/tests/cube_evaluator/compilation.rs @@ -744,6 +744,78 @@ fn test_sql_regular_dimension_wrong_cube_ref() { ]; for dim in wron_dims { - assert!(context.create_dimension(dim).is_err()); + assert!( + context.create_dimension(dim).is_err(), + "Dimension {} should not compile", + dim + ); + } +} + +#[test] +fn test_sql_multi_stage_dimension_wrong_cube_ref() { + let schema = MockSchema::from_yaml_file("compilation_tests/wrong_cube_refs_test.yaml"); + let context = TestContext::new(schema).unwrap(); + let wron_dims = vec![ + "users.multiStageCubeRefInSql", + "users.multiStageOtherCubeRefInSql", + "users.multiStageCubeRefInLongitude", + "users.multiStageCubeRefInLatitude", + "users.multiStageCubeRefInCaseItem", + "users.multiStageCubeRefInCaseSwitchSwitch", + "users.multiStageCubeRefInCaseSwitchItem", + "users.multiStageCubeRefInCaseSwitchElse", + ]; + + for dim in wron_dims { + assert!( + context.create_dimension(dim).is_err(), + "Dimension {} should not compile", + dim + ); + } +} + +#[test] +fn test_sql_multi_stage_dimension_wihtout_member_ref() { + let schema = MockSchema::from_yaml_file("compilation_tests/wrong_cube_refs_test.yaml"); + let context = TestContext::new(schema).unwrap(); + let wron_dims = vec![ + "users.multiStageWithoutRefInSql", + "users.multiStageWithoutRefInLongitude", + "users.multiStageWithoutRefInLatitude", + "users.multiStageWithoutRefInCaseItem", + "users.multiStageWithoutRefInCaseSwitchSwitch", + "users.multiStageWithoutRefInCaseSwitchItem", + "users.multiStageWithoutRefInCaseSwitchElse", + ]; + + for dim in wron_dims { + assert!( + context.create_dimension(dim).is_err(), + "Dimension {} should not compile", + dim + ); + } +} + +#[test] +fn test_sql_multi_stage_measures() { + let schema = MockSchema::from_yaml_file("compilation_tests/wrong_cube_refs_test.yaml"); + let context = TestContext::new(schema).unwrap(); + let wron_dims = vec![ + "users.mOtherCubeRefInSql", + "users.mMultiStageCubeRefInSql", + "users.mMultiStageWithoutRefInCaseSwitchSwitch", + "users.mMultiStageWithoutRefInCaseSwitchItem", + "users.mMultiStageWithoutRefInCaseSwitchElse", + ]; + + for dim in wron_dims { + assert!( + context.create_dimension(dim).is_err(), + "Dimension {} should not compile", + dim + ); } } From f72e3b1c4de8520f95b2c3a6c109f088b9bb82f2 Mon Sep 17 00:00:00 2001 From: Aleksandr Romanenko Date: Thu, 27 Nov 2025 18:45:07 +0100 Subject: [PATCH 11/12] in work --- .../cubesqlplanner/src/planner/sql_evaluator/sql_call_builder.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call_builder.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call_builder.rs index e1105e26ee3d6..866f28a4aacd0 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call_builder.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call_builder.rs @@ -8,7 +8,6 @@ use crate::cube_bridge::security_context::SecurityContext; use crate::planner::sql_evaluator::TimeDimensionSymbol; use crate::planner::GranularityHelper; use cubenativeutils::CubeError; -use itertools::Itertools; use std::rc::Rc; pub struct SqlCallBuilder<'a> { From a3d73d0c7652af1d4a6c1f91c3a7112b57b265b3 Mon Sep 17 00:00:00 2001 From: Aleksandr Romanenko Date: Thu, 27 Nov 2025 18:51:40 +0100 Subject: [PATCH 12/12] in work --- packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts | 1 - .../src/planner/sql_evaluator/sql_call_builder.rs | 2 -- 2 files changed, 3 deletions(-) diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts index 940890927e309..24c298f91947a 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts @@ -800,7 +800,6 @@ export class CubeEvaluator extends CubeSymbols { const member = typeMembers[cubeAndName[1]]; if (member === undefined) { - console.log("!!! ", type); throw new UserError(`'${cubeAndName[1]}' not found for path '${path}'`); } diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call_builder.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call_builder.rs index 866f28a4aacd0..ff1ae4a8dcb89 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call_builder.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call_builder.rs @@ -46,8 +46,6 @@ impl<'a> SqlCallBuilder<'a> { .map(|path| self.build_dependency(cube_name, path)) .collect::, _>>()?; - //Self::validate_deps(cube_name, &deps)?; - let filter_params = template_args .filter_params .iter()