Skip to content

Commit 59210da

Browse files
authored
fix(backend-native): Use the lowest granularity for data-transform td with granularity fallback (#9640)
1 parent 71b2c1d commit 59210da

File tree

1 file changed

+226
-10
lines changed

1 file changed

+226
-10
lines changed

rust/cubeorchestrator/src/query_result_transform.rs

Lines changed: 226 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use serde_json::Value;
1313
use std::{
1414
collections::{HashMap, HashSet},
1515
fmt::Display,
16-
sync::Arc,
16+
sync::{Arc, LazyLock},
1717
};
1818

1919
pub const COMPARE_DATE_RANGE_FIELD: &str = "compareDateRange";
@@ -22,6 +22,20 @@ pub const BLENDING_QUERY_KEY_PREFIX: &str = "time.";
2222
pub const BLENDING_QUERY_RES_SEPARATOR: &str = ".";
2323
pub const MEMBER_SEPARATOR: &str = ".";
2424

25+
pub static GRANULARITY_LEVELS: LazyLock<HashMap<&'static str, u8>> = LazyLock::new(|| {
26+
HashMap::from([
27+
("second", 1),
28+
("minute", 2),
29+
("hour", 3),
30+
("day", 4),
31+
("week", 5),
32+
("month", 6),
33+
("quarter", 7),
34+
("year", 8),
35+
])
36+
});
37+
const DEFAULT_LEVEL_FOR_UNKNOWN: u8 = 10;
38+
2539
/// Transform specified `value` with specified `type` to the network protocol type.
2640
pub fn transform_value(value: DBResponseValue, type_: &str) -> DBResponsePrimitive {
2741
match value {
@@ -168,6 +182,10 @@ pub fn get_members(
168182
return Ok((members_map, members_arr));
169183
}
170184

185+
// FIXME: For now custom granularities are not supported, only common ones.
186+
// There is no granularity type/class implementation in rust yet.
187+
let mut minimal_granularities: HashMap<String, (u8, String)> = HashMap::new();
188+
171189
for column in db_data.columns.iter() {
172190
let member_name = alias_to_member_name_map
173191
.get(column)
@@ -197,11 +215,29 @@ pub fn get_members(
197215
.any(|dim| *dim == MemberOrMemberExpression::Member(calc_member.clone()))
198216
})
199217
{
200-
members_map.insert(calc_member.clone(), column.clone());
201-
members_arr.push(calc_member);
218+
let granularity = path[2];
219+
// For cases when the same dimension with few different granularities is present
220+
//We should not duplicate the dimension without granularity
221+
let level = GRANULARITY_LEVELS
222+
.get(granularity)
223+
.cloned()
224+
.unwrap_or(DEFAULT_LEVEL_FOR_UNKNOWN);
225+
226+
match minimal_granularities.get(&calc_member) {
227+
Some((existing_level, _)) if *existing_level < level => {}
228+
_ => {
229+
minimal_granularities.insert(calc_member, (level, column.clone()));
230+
}
231+
}
202232
}
203233
}
204234

235+
// Handle deprecated time dimensions without granularity
236+
for (member_name, (_, column)) in minimal_granularities {
237+
members_map.insert(member_name.clone(), column.clone());
238+
members_arr.push(member_name.clone());
239+
}
240+
205241
match query_type {
206242
QueryType::CompareDateRangeQuery => {
207243
members_map.insert(
@@ -290,6 +326,10 @@ pub fn get_vanilla_row(
290326
) -> Result<HashMap<String, DBResponsePrimitive>> {
291327
let mut row = HashMap::new();
292328

329+
// FIXME: For now custom granularities are not supported, only common ones.
330+
// There is no granularity type/class implementation in rust yet.
331+
let mut minimal_granularities: HashMap<String, (u8, DBResponsePrimitive)> = HashMap::new();
332+
293333
for (alias, &index) in columns_pos {
294334
if let Some(value) = db_row.get(index) {
295335
let member_name = match alias_to_member_name_map.get(alias) {
@@ -324,23 +364,46 @@ pub fn get_vanilla_row(
324364
row.insert(member_name.clone(), transformed_value.clone());
325365

326366
// Handle deprecated time dimensions without granularity
367+
// Try to collect minimal granularity value for time dimensions without granularity
368+
// as there might be more than one granularity column for the same dimension
327369
let path: Vec<&str> = member_name.split(MEMBER_SEPARATOR).collect();
328-
let member_name_without_granularity =
329-
format!("{}{}{}", path[0], MEMBER_SEPARATOR, path[1]);
330-
if path.len() == 3
331-
&& query.dimensions.as_ref().map_or(true, |dims| {
370+
if path.len() == 3 {
371+
let granularity = path[2];
372+
let member_name_without_granularity =
373+
format!("{}{}{}", path[0], MEMBER_SEPARATOR, path[1]);
374+
375+
// Check that a member without granularity is absent in the query
376+
if query.dimensions.as_ref().map_or(true, |dims| {
332377
!dims.iter().any(|dim| {
333378
*dim == MemberOrMemberExpression::Member(
334379
member_name_without_granularity.clone(),
335380
)
336381
})
337-
})
338-
{
339-
row.insert(member_name_without_granularity, transformed_value);
382+
}) {
383+
let level = GRANULARITY_LEVELS
384+
.get(granularity)
385+
.cloned()
386+
.unwrap_or(DEFAULT_LEVEL_FOR_UNKNOWN);
387+
388+
match minimal_granularities.get(&member_name_without_granularity) {
389+
Some((existing_level, _)) if *existing_level < level => {}
390+
_ => {
391+
minimal_granularities.insert(
392+
member_name_without_granularity,
393+
(level, transformed_value),
394+
);
395+
}
396+
}
397+
}
340398
}
341399
}
342400
}
343401

402+
// Handle deprecated time dimensions without granularity
403+
for (member, (_, value)) in minimal_granularities {
404+
row.insert(member, value);
405+
}
406+
344407
match query_type {
345408
QueryType::CompareDateRangeQuery => {
346409
let date_range_value = get_date_range_value(query.time_dimensions.as_ref())?;
@@ -1271,6 +1334,133 @@ mod tests {
12711334
]
12721335
]
12731336
}
1337+
},
1338+
"blending_query_multiple_granularities": {
1339+
"request": {
1340+
"aliasToMemberNameMap": {
1341+
"e_commerce_records_us2021__avg_discount": "ECommerceRecordsUs2021.avg_discount",
1342+
"e_commerce_records_us2021__order_date_month": "ECommerceRecordsUs2021.orderDate.month",
1343+
"e_commerce_records_us2021__order_date_week": "ECommerceRecordsUs2021.orderDate.week"
1344+
},
1345+
"annotation": {
1346+
"ECommerceRecordsUs2021.avg_discount": {
1347+
"title": "E Commerce Records Us2021 Avg Discount",
1348+
"shortTitle": "Avg Discount",
1349+
"type": "number",
1350+
"drillMembers": [],
1351+
"drillMembersGrouped": {
1352+
"measures": [],
1353+
"dimensions": []
1354+
}
1355+
},
1356+
"ECommerceRecordsUs2021.orderDate.month": {
1357+
"title": "E Commerce Records Us2021 Order Date",
1358+
"shortTitle": "Order Date",
1359+
"type": "time"
1360+
},
1361+
"ECommerceRecordsUs2021.orderDate.week": {
1362+
"title": "E Commerce Records Us2021 Order Date",
1363+
"shortTitle": "Order Date",
1364+
"type": "time"
1365+
},
1366+
"ECommerceRecordsUs2021.orderDate": {
1367+
"title": "E Commerce Records Us2021 Order Date",
1368+
"shortTitle": "Order Date",
1369+
"type": "time"
1370+
}
1371+
},
1372+
"query": {
1373+
"measures": [
1374+
"ECommerceRecordsUs2021.avg_discount"
1375+
],
1376+
"timeDimensions": [
1377+
{
1378+
"dimension": "ECommerceRecordsUs2021.orderDate",
1379+
"granularity": "month",
1380+
"dateRange": [
1381+
"2020-01-01T00:00:00.000",
1382+
"2020-12-30T23:59:59.999"
1383+
]
1384+
},
1385+
{
1386+
"dimension": "ECommerceRecordsUs2021.orderDate",
1387+
"granularity": "week",
1388+
"dateRange": [
1389+
"2020-01-01T00:00:00.000",
1390+
"2020-12-30T23:59:59.999"
1391+
]
1392+
}
1393+
],
1394+
"filters": [
1395+
{
1396+
"operator": "equals",
1397+
"values": [
1398+
"First Class"
1399+
],
1400+
"member": "ECommerceRecordsUs2021.shipMode"
1401+
}
1402+
],
1403+
"limit": 2,
1404+
"rowLimit": 2,
1405+
"timezone": "UTC",
1406+
"order": [],
1407+
"dimensions": []
1408+
},
1409+
"queryType": "blendingQuery"
1410+
},
1411+
"queryResult": [
1412+
{
1413+
"e_commerce_records_us2021__order_date_month": "2020-01-01T00:00:00.000",
1414+
"e_commerce_records_us2021__order_date_week": "2019-12-30T00:00:00.000",
1415+
"e_commerce_records_us2021__avg_discount": "0.28571428571428571429"
1416+
},
1417+
{
1418+
"e_commerce_records_us2021__order_date_month": "2020-02-01T00:00:00.000",
1419+
"e_commerce_records_us2021__order_date_week": "2020-01-27T00:00:00.000",
1420+
"e_commerce_records_us2021__avg_discount": "0.21777777777777777778"
1421+
}
1422+
],
1423+
"finalResultDefault": [
1424+
{
1425+
"ECommerceRecordsUs2021.orderDate.month": "2020-01-01T00:00:00.000",
1426+
"ECommerceRecordsUs2021.orderDate.week": "2019-12-30T00:00:00.000",
1427+
"ECommerceRecordsUs2021.orderDate": "2019-12-30T00:00:00.000",
1428+
"ECommerceRecordsUs2021.avg_discount": "0.28571428571428571429",
1429+
"time.month": "2020-01-01T00:00:00.000"
1430+
},
1431+
{
1432+
"ECommerceRecordsUs2021.orderDate.month": "2020-02-01T00:00:00.000",
1433+
"ECommerceRecordsUs2021.orderDate.week": "2020-01-27T00:00:00.000",
1434+
"ECommerceRecordsUs2021.orderDate": "2020-01-27T00:00:00.000",
1435+
"ECommerceRecordsUs2021.avg_discount": "0.21777777777777777778",
1436+
"time.month": "2020-02-01T00:00:00.000"
1437+
}
1438+
],
1439+
"finalResultCompact": {
1440+
"members": [
1441+
"ECommerceRecordsUs2021.orderDate.month",
1442+
"ECommerceRecordsUs2021.orderDate.week",
1443+
"ECommerceRecordsUs2021.orderDate",
1444+
"ECommerceRecordsUs2021.avg_discount",
1445+
"time.month"
1446+
],
1447+
"dataset": [
1448+
[
1449+
"2020-01-01T00:00:00.000",
1450+
"2019-12-30T00:00:00.000",
1451+
"2019-12-30T00:00:00.000",
1452+
"0.28571428571428571429",
1453+
"2020-01-01T00:00:00.000"
1454+
],
1455+
[
1456+
"2020-02-01T00:00:00.000",
1457+
"2020-01-27T00:00:00.000",
1458+
"2020-01-27T00:00:00.000",
1459+
"0.21777777777777777778",
1460+
"2020-02-01T00:00:00.000"
1461+
]
1462+
]
1463+
}
12741464
}
12751465
}
12761466
"#;
@@ -1919,6 +2109,32 @@ mod tests {
19192109
Ok(())
19202110
}
19212111

2112+
#[test]
2113+
fn test_blending_query_multiple_granularities_default() -> Result<()> {
2114+
let mut test_data = TEST_SUITE_DATA
2115+
.get(&"blending_query_multiple_granularities".to_string())
2116+
.unwrap()
2117+
.clone();
2118+
test_data.request.res_type = Some(ResultType::Default);
2119+
let raw_data = QueryResult::from_js_raw_data(test_data.query_result.clone())?;
2120+
let transformed = TransformedData::transform(&test_data.request, &raw_data)?;
2121+
compare_transformed_data(&transformed, &test_data.final_result_default.unwrap())?;
2122+
Ok(())
2123+
}
2124+
2125+
#[test]
2126+
fn test_blending_query_multiple_granularities_compact() -> Result<()> {
2127+
let mut test_data = TEST_SUITE_DATA
2128+
.get(&"blending_query_multiple_granularities".to_string())
2129+
.unwrap()
2130+
.clone();
2131+
test_data.request.res_type = Some(ResultType::Compact);
2132+
let raw_data = QueryResult::from_js_raw_data(test_data.query_result.clone())?;
2133+
let transformed = TransformedData::transform(&test_data.request, &raw_data)?;
2134+
compare_transformed_data(&transformed, &test_data.final_result_compact.unwrap())?;
2135+
Ok(())
2136+
}
2137+
19222138
#[test]
19232139
fn test_get_members_no_alias_to_member_name_map() -> Result<()> {
19242140
let mut test_data = TEST_SUITE_DATA

0 commit comments

Comments
 (0)