@@ -13,7 +13,7 @@ use serde_json::Value;
13
13
use std:: {
14
14
collections:: { HashMap , HashSet } ,
15
15
fmt:: Display ,
16
- sync:: Arc ,
16
+ sync:: { Arc , LazyLock } ,
17
17
} ;
18
18
19
19
pub const COMPARE_DATE_RANGE_FIELD : & str = "compareDateRange" ;
@@ -22,6 +22,20 @@ pub const BLENDING_QUERY_KEY_PREFIX: &str = "time.";
22
22
pub const BLENDING_QUERY_RES_SEPARATOR : & str = "." ;
23
23
pub const MEMBER_SEPARATOR : & str = "." ;
24
24
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
+
25
39
/// Transform specified `value` with specified `type` to the network protocol type.
26
40
pub fn transform_value ( value : DBResponseValue , type_ : & str ) -> DBResponsePrimitive {
27
41
match value {
@@ -168,6 +182,10 @@ pub fn get_members(
168
182
return Ok ( ( members_map, members_arr) ) ;
169
183
}
170
184
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
+
171
189
for column in db_data. columns . iter ( ) {
172
190
let member_name = alias_to_member_name_map
173
191
. get ( column)
@@ -197,11 +215,29 @@ pub fn get_members(
197
215
. any ( |dim| * dim == MemberOrMemberExpression :: Member ( calc_member. clone ( ) ) )
198
216
} )
199
217
{
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
+ }
202
232
}
203
233
}
204
234
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
+
205
241
match query_type {
206
242
QueryType :: CompareDateRangeQuery => {
207
243
members_map. insert (
@@ -290,6 +326,10 @@ pub fn get_vanilla_row(
290
326
) -> Result < HashMap < String , DBResponsePrimitive > > {
291
327
let mut row = HashMap :: new ( ) ;
292
328
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
+
293
333
for ( alias, & index) in columns_pos {
294
334
if let Some ( value) = db_row. get ( index) {
295
335
let member_name = match alias_to_member_name_map. get ( alias) {
@@ -324,23 +364,46 @@ pub fn get_vanilla_row(
324
364
row. insert ( member_name. clone ( ) , transformed_value. clone ( ) ) ;
325
365
326
366
// 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
327
369
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| {
332
377
!dims. iter ( ) . any ( |dim| {
333
378
* dim == MemberOrMemberExpression :: Member (
334
379
member_name_without_granularity. clone ( ) ,
335
380
)
336
381
} )
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
+ }
340
398
}
341
399
}
342
400
}
343
401
402
+ // Handle deprecated time dimensions without granularity
403
+ for ( member, ( _, value) ) in minimal_granularities {
404
+ row. insert ( member, value) ;
405
+ }
406
+
344
407
match query_type {
345
408
QueryType :: CompareDateRangeQuery => {
346
409
let date_range_value = get_date_range_value ( query. time_dimensions . as_ref ( ) ) ?;
@@ -1271,6 +1334,133 @@ mod tests {
1271
1334
]
1272
1335
]
1273
1336
}
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
+ }
1274
1464
}
1275
1465
}
1276
1466
"# ;
@@ -1919,6 +2109,32 @@ mod tests {
1919
2109
Ok ( ( ) )
1920
2110
}
1921
2111
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
+
1922
2138
#[ test]
1923
2139
fn test_get_members_no_alias_to_member_name_map ( ) -> Result < ( ) > {
1924
2140
let mut test_data = TEST_SUITE_DATA
0 commit comments