@@ -967,6 +967,213 @@ describe('PreAggregations', () => {
967967 ]
968968 });
969969
970+ // Models with transitive joins for rollupJoin matching
971+ cube('merchant_dims', {
972+ sql: \`
973+ SELECT 101 AS merchant_sk, 'M1' AS merchant_id
974+ UNION ALL
975+ SELECT 102 AS merchant_sk, 'M2' AS merchant_id
976+ \`,
977+
978+ dimensions: {
979+ merchant_sk: {
980+ sql: 'merchant_sk',
981+ type: 'number',
982+ primary_key: true
983+ },
984+ merchant_id: {
985+ sql: 'merchant_id',
986+ type: 'string'
987+ }
988+ }
989+ });
990+
991+ cube('product_dims', {
992+ sql: \`
993+ SELECT 201 AS product_sk, 'P1' AS product_id
994+ UNION ALL
995+ SELECT 202 AS product_sk, 'P2' AS product_id
996+ \`,
997+
998+ dimensions: {
999+ product_sk: {
1000+ sql: 'product_sk',
1001+ type: 'number',
1002+ primary_key: true
1003+ },
1004+ product_id: {
1005+ sql: 'product_id',
1006+ type: 'string'
1007+ }
1008+ }
1009+ });
1010+
1011+ cube('merchant_and_product_dims', {
1012+ sql: \`
1013+ SELECT 'M1' AS merchant_id, 'P1' AS product_id, 'Organic' AS acquisition_channel, 'SOLD' AS status
1014+ UNION ALL
1015+ SELECT 'M1' AS merchant_id, 'P2' AS product_id, 'Paid' AS acquisition_channel, 'PAID' AS status
1016+ UNION ALL
1017+ SELECT 'M2' AS merchant_id, 'P1' AS product_id, 'Referral' AS acquisition_channel, 'RETURNED' AS status
1018+ \`,
1019+
1020+ dimensions: {
1021+ product_id: {
1022+ sql: 'product_id',
1023+ type: 'string',
1024+ primary_key: true
1025+ },
1026+ merchant_id: {
1027+ sql: 'merchant_id',
1028+ type: 'string',
1029+ primary_key: true
1030+ },
1031+ status: {
1032+ sql: 'status',
1033+ type: 'string'
1034+ },
1035+ acquisition_channel: {
1036+ sql: 'acquisition_channel',
1037+ type: 'string'
1038+ }
1039+ },
1040+
1041+ pre_aggregations: {
1042+ bridge_rollup: {
1043+ dimensions: [
1044+ merchant_id,
1045+ product_id,
1046+ acquisition_channel,
1047+ status
1048+ ]
1049+ }
1050+ }
1051+ });
1052+
1053+ cube('other_facts', {
1054+ sql: \`
1055+ SELECT 1 AS id, 1 AS fact_id, 'OF1' AS fact
1056+ UNION ALL
1057+ SELECT 2 AS id, 2 AS fact_id, 'OF2' AS fact
1058+ UNION ALL
1059+ SELECT 3 AS id, 3 AS fact_id, 'OF3' AS fact
1060+ \`,
1061+
1062+ dimensions: {
1063+ other_fact_id: {
1064+ sql: 'id',
1065+ type: 'number',
1066+ primary_key: true
1067+ },
1068+ fact_id: {
1069+ sql: 'fact_id',
1070+ type: 'number'
1071+ },
1072+ fact: {
1073+ sql: 'fact',
1074+ type: 'string'
1075+ }
1076+ },
1077+
1078+ pre_aggregations: {
1079+ bridge_rollup: {
1080+ dimensions: [
1081+ fact_id,
1082+ fact
1083+ ]
1084+ }
1085+ }
1086+
1087+ });
1088+
1089+ cube('test_facts', {
1090+ sql: \`
1091+ SELECT 1 AS id, 101 AS merchant_sk, 201 AS product_sk, 100 AS amount
1092+ UNION ALL
1093+ SELECT 2 AS id, 101 AS merchant_sk, 202 AS product_sk, 150 AS amount
1094+ UNION ALL
1095+ SELECT 3 AS id, 102 AS merchant_sk, 201 AS product_sk, 200 AS amount
1096+ \`,
1097+
1098+ joins: {
1099+ merchant_dims: {
1100+ relationship: 'many_to_one',
1101+ sql: \`\${CUBE.merchant_sk} = \${merchant_dims.merchant_sk}\`
1102+ },
1103+ product_dims: {
1104+ relationship: 'many_to_one',
1105+ sql: \`\${CUBE.product_sk} = \${product_dims.product_sk}\`
1106+ },
1107+ // Transitive join - depends on merchant_dims and product_dims
1108+ merchant_and_product_dims: {
1109+ relationship: 'many_to_one',
1110+ sql: \`\${merchant_dims.merchant_id} = \${merchant_and_product_dims.merchant_id} AND \${product_dims.product_id} = \${merchant_and_product_dims.product_id}\`
1111+ },
1112+ other_facts: {
1113+ relationship: 'one_to_many',
1114+ sql: \`\${CUBE.id} = \${other_facts.fact_id}\`
1115+ },
1116+ },
1117+
1118+ dimensions: {
1119+ id: {
1120+ sql: 'id',
1121+ type: 'number',
1122+ primary_key: true
1123+ },
1124+ merchant_sk: {
1125+ sql: 'merchant_sk',
1126+ type: 'number'
1127+ },
1128+ product_sk: {
1129+ sql: 'product_sk',
1130+ type: 'number'
1131+ },
1132+ acquisition_channel: {
1133+ sql: \`\${merchant_and_product_dims.acquisition_channel}\`,
1134+ type: 'string'
1135+ }
1136+ },
1137+
1138+ measures: {
1139+ amount_sum: {
1140+ sql: 'amount',
1141+ type: 'sum'
1142+ }
1143+ },
1144+
1145+ pre_aggregations: {
1146+ facts_rollup: {
1147+ dimensions: [
1148+ id,
1149+ merchant_sk,
1150+ merchant_dims.merchant_sk,
1151+ merchant_dims.merchant_id,
1152+ merchant_and_product_dims.merchant_id,
1153+ product_sk,
1154+ product_dims.product_sk,
1155+ product_dims.product_id,
1156+ merchant_and_product_dims.product_id,
1157+ acquisition_channel,
1158+ merchant_and_product_dims.status
1159+ ]
1160+ },
1161+ rollupJoinTransitive: {
1162+ type: 'rollupJoin',
1163+ dimensions: [
1164+ merchant_sk,
1165+ product_sk,
1166+ merchant_and_product_dims.status,
1167+ other_facts.fact
1168+ ],
1169+ rollups: [
1170+ facts_rollup,
1171+ other_facts.bridge_rollup
1172+ ]
1173+ }
1174+ }
1175+ });
1176+
9701177 ` ) ;
9711178
9721179 it ( 'simple pre-aggregation' , async ( ) => {
@@ -3276,4 +3483,58 @@ describe('PreAggregations', () => {
32763483 } ) ;
32773484 } ) ;
32783485 }
3486+
3487+ it ( 'rollupJoin pre-aggregation matching with transitive joins' , async ( ) => {
3488+ await compiler . compile ( ) ;
3489+
3490+ const query = new PostgresQuery ( { joinGraph, cubeEvaluator, compiler } , {
3491+ dimensions : [
3492+ 'test_facts.merchant_sk' ,
3493+ 'test_facts.product_sk' ,
3494+ 'merchant_and_product_dims.status' ,
3495+ 'other_facts.fact'
3496+ ] ,
3497+ timezone : 'America/Los_Angeles' ,
3498+ preAggregationsSchema : ''
3499+ } ) ;
3500+
3501+ const queryAndParams = query . buildSqlAndParams ( ) ;
3502+ console . log ( queryAndParams ) ;
3503+ const preAggregationsDescription : any = query . preAggregations ?. preAggregationsDescription ( ) ;
3504+ console . log ( JSON . stringify ( preAggregationsDescription , null , 2 ) ) ;
3505+
3506+ // Verify that both rollups are included in the description
3507+ expect ( preAggregationsDescription . length ) . toBe ( 2 ) ;
3508+ const factsRollup = preAggregationsDescription . find ( p => p . preAggregationId === 'test_facts.facts_rollup' ) ;
3509+ const bridgeRollup = preAggregationsDescription . find ( p => p . preAggregationId === 'other_facts.bridge_rollup' ) ;
3510+ expect ( factsRollup ) . toBeDefined ( ) ;
3511+ expect ( bridgeRollup ) . toBeDefined ( ) ;
3512+
3513+ // Verify that the rollupJoin pre-aggregation can be used for the query
3514+ expect ( query . preAggregations ?. preAggregationForQuery ?. canUsePreAggregation ) . toEqual ( true ) ;
3515+ expect ( query . preAggregations ?. preAggregationForQuery ?. preAggregationName ) . toEqual ( 'rollupJoinTransitive' ) ;
3516+
3517+ return dbRunner . evaluateQueryWithPreAggregations ( query ) . then ( res => {
3518+ expect ( res ) . toEqual ( [
3519+ {
3520+ merchant_and_product_dims__status : 'SOLD' ,
3521+ other_facts__fact : 'OF1' ,
3522+ test_facts__merchant_sk : 101 ,
3523+ test_facts__product_sk : 201 ,
3524+ } ,
3525+ {
3526+ merchant_and_product_dims__status : 'PAID' ,
3527+ other_facts__fact : 'OF2' ,
3528+ test_facts__merchant_sk : 101 ,
3529+ test_facts__product_sk : 202 ,
3530+ } ,
3531+ {
3532+ merchant_and_product_dims__status : 'RETURNED' ,
3533+ other_facts__fact : 'OF3' ,
3534+ test_facts__merchant_sk : 102 ,
3535+ test_facts__product_sk : 201 ,
3536+ } ,
3537+ ] ) ;
3538+ } ) ;
3539+ } ) ;
32793540} ) ;
0 commit comments