Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use super::super::context::PushDownBuilderContext;
use super::super::{LogicalNodeProcessor, ProcessableNode};
use crate::logical_plan::{MultiStageCalculationWindowFunction, MultiStageMeasureCalculation};
use crate::logical_plan::{
MultiStageCalculationType, MultiStageCalculationWindowFunction, MultiStageMeasureCalculation,
};
use crate::physical_plan::ReferencesBuilder;
use crate::physical_plan::{Expr, MemberExpression, QueryPlan, SelectBuilder};
use crate::physical_plan_builder::PhysicalPlanBuilder;
Expand Down Expand Up @@ -58,14 +60,27 @@ impl<'a> LogicalNodeProcessor<'a, MultiStageMeasureCalculation>
select_builder.add_projection_member(measure, alias);
}

if !measure_calculation.is_ungrouped() {
// A non-windowed Aggregate stage projects plain aggregate functions
// (e.g. `sum(...)`) next to its dimensions, so the select must be
// grouped by those dimensions regardless of the query-level ungrouped
// flag — the enclosing FullKeyAggregate join broadcasts the stage
// output back to detail rows, keeping ungrouped semantics intact.
// Rank / Calculate / windowed stages render window functions or plain
// expressions and stay valid without GROUP BY.
let renders_plain_aggregates = measure_calculation.calculation_type()
== &MultiStageCalculationType::Aggregate
&& measure_calculation.window_function_to_use()
== &MultiStageCalculationWindowFunction::None;
if !measure_calculation.is_ungrouped() || renders_plain_aggregates {
let group_by = all_dimensions
.iter()
.map(|dim| -> Result<_, CubeError> {
Ok(Expr::Member(MemberExpression::new(dim.clone())))
})
.collect::<Result<Vec<_>, _>>()?;
select_builder.set_group_by(group_by);
}
if !measure_calculation.is_ungrouped() {
select_builder.set_order_by(
self.builder
.make_order_by(measure_calculation.schema(), measure_calculation.order_by())?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,16 @@ cubes:
- orders.status
- orders.created_at.month

# group_by-locked sum over a non-sum-rollable input
# (count_distinct): not window-path eligible, so the Aggregate
# stage renders a plain sum() over the FullKeyAggregate join.
- name: unique_customers_group_by_status
type: sum
sql: "{CUBE.unique_customers}"
multi_stage: true
group_by:
- orders.status

- name: amount_grain_keep_only_status
type: sum
sql: "{CUBE.total_amount}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,37 @@ async fn test_ungrouped_with_dimension() {
}
}

// An ungrouped query selecting a group_by-locked Aggregate measure whose
// input is not sum-rollable (count_distinct) goes through the
// FullKeyAggregate JOIN model: the measure stage projects the query
// dimensions next to a plain sum(). That stage must be grouped even though
// the query itself is ungrouped — otherwise databases enforcing SQL
// grouping rules (e.g. Postgres) reject the statement. This is exactly the
// shape BI tools emit when browsing a table through the SQL API.
#[tokio::test(flavor = "multi_thread")]
async fn test_ungrouped_group_by_locked_aggregate_with_dimension() {
let ctx = create_context();

let query = indoc! {r#"
measures:
- orders.unique_customers_group_by_status
dimensions:
- orders.category
ungrouped: true
"#};

let sql = ctx.build_sql(query).unwrap();
assert!(
sql.contains("GROUP BY"),
"the Aggregate stage projects sum() next to bare dimension columns \
and must be grouped even in an ungrouped query, got: {sql}"
);

if let Some(result) = ctx.try_execute_pg(query, SEED).await {
insta::assert_snapshot!(result);
}
}

// Cross-product issue: ungrouped mode prevents aggregation in both
// current and shifted CTEs. The JOIN between them produces a cartesian
// product — Feb has 5 current rows × 5 shifted Jan rows = 25 rows,
Expand Down
Loading