@@ -13,7 +13,7 @@ use serde_json::Value;
1313use std:: {
1414 collections:: { HashMap , HashSet } ,
1515 fmt:: Display ,
16- sync:: Arc ,
16+ sync:: { Arc , LazyLock } ,
1717} ;
1818
1919pub const COMPARE_DATE_RANGE_FIELD : & str = "compareDateRange" ;
@@ -22,6 +22,20 @@ pub const BLENDING_QUERY_KEY_PREFIX: &str = "time.";
2222pub const BLENDING_QUERY_RES_SEPARATOR : & str = "." ;
2323pub 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.
2640pub 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