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
Expand Up @@ -1066,6 +1066,57 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
}
]));

it('member to alias for granularized time dimension', async () => runQueryTest({
measures: [
'visitors.visitor_revenue',
'visitors.visitor_count',
'visitors.per_visitor_revenue'
],
dimensions: [
'visitors.source'
],
timeDimensions: [{
dimension: 'visitors.created_at',
dateRange: ['2017-01-01', '2017-01-30'],
granularity: 'month'
}],
timezone: 'America/Los_Angeles',
// SQL API keys a granularized override `{member}.{granularity}` (dotted) and
// references the CubeScan column by it. The native planner must honor it
// instead of defaulting to `{base alias}_{granularity}`.
memberToAlias: {
'visitors.visitor_revenue': 'custom_revenue',
'visitors.visitor_count': 'custom_count',
'visitors.source': 'custom_source',
'visitors.created_at.month': 'custom_created_at_month',
},
order: []
},

[
{
custom_source: 'google',
custom_created_at_month: '2017-01-01T00:00:00.000Z',
custom_revenue: null,
custom_count: '1',
visitors__per_visitor_revenue: null
},
{
custom_source: 'some',
custom_created_at_month: '2017-01-01T00:00:00.000Z',
custom_revenue: '300',
custom_count: '2',
visitors__per_visitor_revenue: '150'
},
{
custom_source: null,
custom_created_at_month: '2017-01-01T00:00:00.000Z',
custom_revenue: null,
custom_count: '2',
visitors__per_visitor_revenue: null
}
]));

it('running total', async () => {
await compiler.compile();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,26 @@ impl QueryPropertiesCompiler {
} else {
None
};
Ok(MemberSymbol::new_time_dimension(TimeDimensionSymbol::new(
base_symbol,
d.granularity.clone(),
granularity_obj,
date_range_tuple,
)))
// Honor an explicit `memberToAlias` override for the granularized
// member. The SQL API (cubesql) keys it `{member}.{granularity}`
// (dotted) and references the CubeScan column by that alias; the
// default `{base alias}_{granularity}` would otherwise mismatch.
let alias_override = d.granularity.as_ref().and_then(|granularity| {
evaluator_compiler.alias_for_member(&format!(
"{}.{}",
base_symbol.full_name(),
granularity
))
});
Ok(MemberSymbol::new_time_dimension(
TimeDimensionSymbol::new_with_alias(
base_symbol,
d.granularity.clone(),
granularity_obj,
date_range_tuple,
alias_override,
),
))
})
.collect()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,28 @@ impl TimeDimensionSymbol {
granularity: Option<String>,
granularity_obj: Option<Granularity>,
date_range: Option<(String, String)>,
) -> Rc<Self> {
Self::new_with_alias(base_symbol, granularity, granularity_obj, date_range, None)
}

/// Like [`Self::new`] but with an explicit alias override (e.g. the SQL
/// API's `memberToAlias` entry for the granularized member). When `None`,
/// the alias falls back to `{base alias}_{granularity}`.
pub fn new_with_alias(
base_symbol: Rc<MemberSymbol>,
granularity: Option<String>,
granularity_obj: Option<Granularity>,
date_range: Option<(String, String)>,
alias_override: Option<String>,
) -> Rc<Self> {
let name_suffix = if let Some(granularity) = &granularity {
granularity.clone()
} else {
"day".to_string()
};
let full_name = format!("{}_{}", base_symbol.full_name(), name_suffix);
let alias = format!("{}_{}", base_symbol.alias(), name_suffix);
let alias =
alias_override.unwrap_or_else(|| format!("{}_{}", base_symbol.alias(), name_suffix));
let compiled_path = CompiledMemberPath::new(
base_symbol.compiled_path().cube().clone(),
full_name,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::test_fixtures::cube_bridge::{members_from_strings, MockBaseQueryOptions, MockSchema};
use crate::test_fixtures::test_utils::TestContext;
use cubenativeutils::CubeError;
use indoc::indoc;
use std::rc::Rc;

Expand Down Expand Up @@ -40,6 +41,40 @@ fn test_member_to_alias() {
);
}

#[test]
fn test_member_to_alias_time_dimension_granularity() -> Result<(), CubeError> {
let schema = MockSchema::from_yaml_file("common/visitors.yaml");
let test_context = TestContext::new(schema)?;

// The SQL API references a granularized time-dimension column by an alias
// sent via `memberToAlias` keyed `{member}.{granularity}`. The planner must
// honor it instead of defaulting to `{base alias}_{granularity}`.
let query_yaml = indoc! {r#"
measures:
- visitors.count
time_dimensions:
- dimension: visitors.created_at
granularity: month
memberToAlias:
visitors.created_at.month: "td_month_alias"
"#};

let sql = test_context.build_sql(query_yaml)?;

// The override must be used as the projected alias …
assert!(
sql.contains("\"td_month_alias\""),
"expected granularized memberToAlias override, got: {sql}"
);
// … instead of the default `{base alias}_{granularity}`.
assert!(
!sql.contains("visitors__created_at_month"),
"should not fall back to default granularized alias, got: {sql}"
);

Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn test_simple_join_sql() {
let schema = MockSchema::from_yaml_file("common/diamond_joins.yaml");
Expand Down
Loading