diff --git a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/common/integration_multi_fact.yaml b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/common/integration_multi_fact.yaml index 6acb720eeadc9..5c100fb1d9f9d 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/common/integration_multi_fact.yaml +++ b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/common/integration_multi_fact.yaml @@ -1,4 +1,15 @@ cubes: + - name: regions + sql: "SELECT * FROM regions" + dimensions: + - name: id + type: number + sql: id + primary_key: true + - name: name + type: string + sql: name + - name: customers sql: "SELECT * FROM customers" joins: @@ -11,6 +22,9 @@ cubes: - name: reviews relationship: one_to_many sql: "{customers}.id = {reviews.customer_id}" + - name: regions + relationship: many_to_one + sql: "{customers}.region_id = {regions.id}" dimensions: - name: id type: number @@ -28,6 +42,14 @@ cubes: measures: - name: count type: count + - name: total_lifetime_value + type: sum + sql: lifetime_value + - name: total_lifetime_value_for_east + type: sum + sql: lifetime_value + filters: + - sql: "{regions.name} = 'East'" - name: orders sql: "SELECT * FROM orders" diff --git a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/common/integration_rolling_window_multi_fact.yaml b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/common/integration_rolling_window_multi_fact.yaml index 0eee4d330a09e..a986a81f17539 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/common/integration_rolling_window_multi_fact.yaml +++ b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/common/integration_rolling_window_multi_fact.yaml @@ -25,6 +25,9 @@ cubes: measures: - name: count type: count + - name: total_lifetime_value + type: sum + sql: lifetime_value - name: payments sql: "SELECT * FROM mf_payments" @@ -39,6 +42,9 @@ cubes: - name: amount type: number sql: amount + - name: payment_type + type: string + sql: payment_type - name: created_at type: time sql: created_at diff --git a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/seeds/integration_multi_fact_tables.sql b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/seeds/integration_multi_fact_tables.sql index 4b7f60a521a2a..d2de79405a19c 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/seeds/integration_multi_fact_tables.sql +++ b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/seeds/integration_multi_fact_tables.sql @@ -2,11 +2,19 @@ DROP TABLE IF EXISTS reviews CASCADE; DROP TABLE IF EXISTS returns CASCADE; DROP TABLE IF EXISTS orders CASCADE; DROP TABLE IF EXISTS customers CASCADE; +DROP TABLE IF EXISTS regions CASCADE; + +CREATE TABLE regions ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL +); CREATE TABLE customers ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, city TEXT NOT NULL, + region_id INTEGER NOT NULL REFERENCES regions(id), + lifetime_value NUMERIC(10, 2) NOT NULL, created_at TIMESTAMP NOT NULL ); @@ -33,11 +41,15 @@ CREATE TABLE reviews ( created_at TIMESTAMP NOT NULL ); -INSERT INTO customers (id, name, city, created_at) VALUES - (1, 'Alice', 'New York', '2025-01-15 10:00:00'), - (2, 'Bob', 'Boston', '2025-02-01 12:00:00'), - (3, 'Charlie', 'Chicago', '2025-02-15 09:00:00'), - (4, 'Diana', 'New York', '2025-03-01 08:00:00'); +INSERT INTO regions (id, name) VALUES + (1, 'East'), + (2, 'Midwest'); + +INSERT INTO customers (id, name, city, region_id, lifetime_value, created_at) VALUES + (1, 'Alice', 'New York', 1, 1000.00, '2025-01-15 10:00:00'), + (2, 'Bob', 'Boston', 1, 2000.00, '2025-02-01 12:00:00'), + (3, 'Charlie', 'Chicago', 2, 500.00, '2025-02-15 09:00:00'), + (4, 'Diana', 'New York', 1, 1500.00, '2025-03-01 08:00:00'); INSERT INTO orders (id, customer_id, status, amount, created_at) VALUES (1, 1, 'completed', 100.00, '2025-03-01 10:00:00'), diff --git a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/seeds/integration_rolling_window_multi_fact_tables.sql b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/seeds/integration_rolling_window_multi_fact_tables.sql index dcab3fe226563..7b89a37b8a967 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/seeds/integration_rolling_window_multi_fact_tables.sql +++ b/rust/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/seeds/integration_rolling_window_multi_fact_tables.sql @@ -6,6 +6,7 @@ CREATE TABLE mf_customers ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, city TEXT, + lifetime_value NUMERIC(10, 2) NOT NULL, registered_at TIMESTAMP NOT NULL ); @@ -13,6 +14,7 @@ CREATE TABLE mf_payments ( id INTEGER PRIMARY KEY, customer_id INTEGER NOT NULL REFERENCES mf_customers(id), amount NUMERIC(10, 2) NOT NULL, + payment_type TEXT NOT NULL, created_at TIMESTAMP NOT NULL ); @@ -23,19 +25,19 @@ CREATE TABLE mf_messages ( ); -- Customers registered on different days in January 2024 -INSERT INTO mf_customers (id, name, city, registered_at) VALUES - (1, 'Alice', 'New York', '2024-01-10 10:00:00'), - (2, 'Bob', 'San Francisco', '2024-01-15 12:00:00'), - (3, 'Charlie', 'Boston', '2024-01-20 09:00:00'); +INSERT INTO mf_customers (id, name, city, lifetime_value, registered_at) VALUES + (1, 'Alice', 'New York', 500.00, '2024-01-10 10:00:00'), + (2, 'Bob', 'San Francisco', 300.00, '2024-01-15 12:00:00'), + (3, 'Charlie', 'Boston', 200.00, '2024-01-20 09:00:00'); -- Payments: each customer pays at various times after registration -INSERT INTO mf_payments (id, customer_id, amount, created_at) VALUES - (1, 1, 100.00, '2024-01-11 10:00:00'), - (2, 1, 200.00, '2024-01-18 14:00:00'), - (3, 2, 150.00, '2024-01-16 11:00:00'), - (4, 2, 50.00, '2024-01-22 09:00:00'), - (5, 3, 75.00, '2024-01-21 16:00:00'), - (6, 3, 125.00, '2024-01-24 10:00:00'); +INSERT INTO mf_payments (id, customer_id, amount, payment_type, created_at) VALUES + (1, 1, 100.00, 'card', '2024-01-11 10:00:00'), + (2, 1, 200.00, 'cash', '2024-01-18 14:00:00'), + (3, 2, 150.00, 'card', '2024-01-16 11:00:00'), + (4, 2, 50.00, 'card', '2024-01-22 09:00:00'), + (5, 3, 75.00, 'cash', '2024-01-21 16:00:00'), + (6, 3, 125.00, 'card', '2024-01-24 10:00:00'); -- Messages: support messages from each customer INSERT INTO mf_messages (id, customer_id, created_at) VALUES diff --git a/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/combinations.rs b/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/combinations.rs index 325e9880cb970..231f8fac81204 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/combinations.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/combinations.rs @@ -96,6 +96,45 @@ async fn test_multiplied_with_join_filter_segment_time() { } } +// 10.2b: Same as 10.2 but with SUM hub measure → AggregateMultipliedSubquery +// instead of count (which just becomes count distinct) +#[tokio::test(flavor = "multi_thread")] +async fn test_multiplied_aggregate_with_join_filter_segment_time() { + let ctx = create_multi_fact_context(); + + let query = indoc! {" + measures: + - customers.total_lifetime_value + - orders.count + dimensions: + - customers.name + segments: + - orders.completed_orders + filters: + - dimension: customers.city + operator: equals + values: + - New York + time_dimensions: + - dimension: orders.created_at + granularity: month + dateRange: + - \"2025-03-01\" + - \"2025-03-31\" + order: + - id: customers.name + "}; + + ctx.build_sql(query).unwrap(); + + if let Some(result) = ctx + .try_execute_pg(query, "integration_multi_fact_tables.sql") + .await + { + insta::assert_snapshot!(result); + } +} + // 10.3: Subquery dimension filter + time dateRange #[tokio::test(flavor = "multi_thread")] async fn test_subquery_dim_filter_with_time_range() { diff --git a/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/multi_fact.rs b/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/multi_fact.rs index b930f098ddbea..7bb92826ae475 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/multi_fact.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/multi_fact.rs @@ -463,6 +463,54 @@ async fn test_multiplied_with_order_and_limit() { } } +#[tokio::test(flavor = "multi_thread")] +async fn test_multiplied_aggregate_hub_sum_measure() { + let ctx = create_context(); + + // customers.total_lifetime_value (SUM) by orders.status + // customers is multiplied in customers→orders join (one_to_many) + // SUM is not additive in multiplied context → AggregateMultipliedSubquery + // Measure references only customers → source = Cube (no MeasureSubquery) + let query = indoc! {" + measures: + - customers.total_lifetime_value + dimensions: + - orders.status + order: + - id: orders.status + "}; + + ctx.build_sql(query).unwrap(); + + if let Some(result) = ctx.try_execute_pg(query, SEED).await { + insta::assert_snapshot!(result); + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_multiplied_aggregate_with_measure_subquery() { + let ctx = create_context(); + + // customers.total_lifetime_value_for_east (SUM with filter {regions.name} = 'East') + // customers is multiplied in customers→orders join → AggregateMultipliedSubquery + // Measure filter references regions cube → MeasureSubquery (customers→regions join, + // where customers is NOT multiplied since many_to_one) + let query = indoc! {" + measures: + - customers.total_lifetime_value_for_east + dimensions: + - orders.status + order: + - id: orders.status + "}; + + ctx.build_sql(query).unwrap(); + + if let Some(result) = ctx.try_execute_pg(query, SEED).await { + insta::assert_snapshot!(result); + } +} + #[tokio::test(flavor = "multi_thread")] async fn test_non_multiplied_multi_join() { let ctx = create_context(); diff --git a/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/rolling_window/multi_fact.rs b/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/rolling_window/multi_fact.rs index e6a1215b66ecb..3fc2862c5cc97 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/rolling_window/multi_fact.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/rolling_window/multi_fact.rs @@ -86,6 +86,37 @@ async fn test_two_rolling_aggregated_by_day() { } } +#[tokio::test(flavor = "multi_thread")] +async fn test_rolling_with_multiplied_aggregate_hub_measure() { + let ctx = create_context(); + + // Combines multi-stage (rolling_sum_7d) with AggregateMultipliedSubquery + // (customers.total_lifetime_value SUM on hub, multiplied by payments). + // payments.payment_type dimension forces customers→payments join, + // making customers multiplied → AggregateMultipliedSubquery for the SUM measure. + let query = indoc! {r#" + measures: + - payments.rolling_sum_7d + - customers.total_lifetime_value + dimensions: + - payments.payment_type + time_dimensions: + - dimension: customers.registered_at + granularity: day + dateRange: + - "2024-01-10" + - "2024-01-25" + order: + - id: payments.payment_type + "#}; + + ctx.build_sql(query).unwrap(); + + if let Some(result) = ctx.try_execute_pg(query, SEED).await { + insta::assert_snapshot!(result); + } +} + #[tokio::test(flavor = "multi_thread")] async fn test_two_rolling_with_shared_dimension_and_filter() { let ctx = create_context(); diff --git a/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/rolling_window/snapshots/cubesqlplanner__tests__integration__rolling_window__multi_fact__rolling_with_multiplied_aggregate_hub_measure.snap b/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/rolling_window/snapshots/cubesqlplanner__tests__integration__rolling_window__multi_fact__rolling_with_multiplied_aggregate_hub_measure.snap new file mode 100644 index 0000000000000..a9d36e68fa7d3 --- /dev/null +++ b/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/rolling_window/snapshots/cubesqlplanner__tests__integration__rolling_window__multi_fact__rolling_with_multiplied_aggregate_hub_measure.snap @@ -0,0 +1,35 @@ +--- +source: cubesqlplanner/src/tests/integration/rolling_window/multi_fact.rs +expression: result +--- +payments__payment_type | customers__registered_at_day | payments__rolling_sum_7d | customers__total_lifetime_value +-----------------------+------------------------------+--------------------------+-------------------------------- +card | 2024-01-10 00:00:00 | 100.00 | 500.00 +card | 2024-01-11 00:00:00 | 100.00 | NULL +card | 2024-01-12 00:00:00 | 100.00 | NULL +card | 2024-01-13 00:00:00 | 100.00 | NULL +card | 2024-01-14 00:00:00 | 100.00 | NULL +card | 2024-01-15 00:00:00 | 300.00 | 300.00 +card | 2024-01-16 00:00:00 | 300.00 | NULL +card | 2024-01-17 00:00:00 | 200.00 | NULL +card | 2024-01-18 00:00:00 | 200.00 | NULL +card | 2024-01-19 00:00:00 | 200.00 | NULL +card | 2024-01-20 00:00:00 | 325.00 | 200.00 +card | 2024-01-21 00:00:00 | 325.00 | NULL +card | 2024-01-22 00:00:00 | 125.00 | NULL +card | 2024-01-23 00:00:00 | 125.00 | NULL +card | 2024-01-24 00:00:00 | 125.00 | NULL +card | 2024-01-25 00:00:00 | 125.00 | NULL +cash | 2024-01-10 00:00:00 | 200.00 | 500.00 +cash | 2024-01-11 00:00:00 | 200.00 | NULL +cash | 2024-01-12 00:00:00 | 200.00 | NULL +cash | 2024-01-13 00:00:00 | 200.00 | NULL +cash | 2024-01-14 00:00:00 | 200.00 | NULL +cash | 2024-01-15 00:00:00 | 200.00 | NULL +cash | 2024-01-16 00:00:00 | 200.00 | NULL +cash | 2024-01-20 00:00:00 | 75.00 | 200.00 +cash | 2024-01-21 00:00:00 | 75.00 | NULL +cash | 2024-01-22 00:00:00 | 75.00 | NULL +cash | 2024-01-23 00:00:00 | 75.00 | NULL +cash | 2024-01-24 00:00:00 | 75.00 | NULL +cash | 2024-01-25 00:00:00 | 75.00 | NULL diff --git a/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__combinations__multiplied_aggregate_with_join_filter_segment_time.snap b/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__combinations__multiplied_aggregate_with_join_filter_segment_time.snap new file mode 100644 index 0000000000000..e9eface678076 --- /dev/null +++ b/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__combinations__multiplied_aggregate_with_join_filter_segment_time.snap @@ -0,0 +1,8 @@ +--- +source: cubesqlplanner/src/tests/integration/combinations.rs +expression: result +--- +customers__name | orders__created_at_month | customers__total_lifetime_value | orders__count +----------------+--------------------------+---------------------------------+-------------- +Alice | 2025-03-01 00:00:00 | 1000.00 | 2 +Diana | 2025-03-01 00:00:00 | 1500.00 | 1 diff --git a/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__multi_fact__multiplied_aggregate_hub_sum_measure.snap b/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__multi_fact__multiplied_aggregate_hub_sum_measure.snap new file mode 100644 index 0000000000000..5b6f57b4b5087 --- /dev/null +++ b/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__multi_fact__multiplied_aggregate_hub_sum_measure.snap @@ -0,0 +1,9 @@ +--- +source: cubesqlplanner/src/tests/integration/multi_fact.rs +expression: result +--- +orders__status | customers__total_lifetime_value +---------------+-------------------------------- +completed | 4500.00 +pending | 3000.00 +NULL | 500.00 diff --git a/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__multi_fact__multiplied_aggregate_with_measure_subquery.snap b/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__multi_fact__multiplied_aggregate_with_measure_subquery.snap new file mode 100644 index 0000000000000..0ebac4870368e --- /dev/null +++ b/rust/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__multi_fact__multiplied_aggregate_with_measure_subquery.snap @@ -0,0 +1,9 @@ +--- +source: cubesqlplanner/src/tests/integration/multi_fact.rs +expression: result +--- +orders__status | customers__total_lifetime_value_for_east +---------------+----------------------------------------- +completed | 4500.00 +pending | 3000.00 +NULL | NULL