@@ -925,6 +925,213 @@ describe('PreAggregations', () => {
925925 ]
926926 });
927927
928+ // Models with transitive joins for rollupJoin matching
929+ cube('merchant_dims', {
930+ sql: \`
931+ SELECT 101 AS merchant_sk, 'M1' AS merchant_id
932+ UNION ALL
933+ SELECT 102 AS merchant_sk, 'M2' AS merchant_id
934+ \`,
935+
936+ dimensions: {
937+ merchant_sk: {
938+ sql: 'merchant_sk',
939+ type: 'number',
940+ primary_key: true
941+ },
942+ merchant_id: {
943+ sql: 'merchant_id',
944+ type: 'string'
945+ }
946+ }
947+ });
948+
949+ cube('product_dims', {
950+ sql: \`
951+ SELECT 201 AS product_sk, 'P1' AS product_id
952+ UNION ALL
953+ SELECT 202 AS product_sk, 'P2' AS product_id
954+ \`,
955+
956+ dimensions: {
957+ product_sk: {
958+ sql: 'product_sk',
959+ type: 'number',
960+ primary_key: true
961+ },
962+ product_id: {
963+ sql: 'product_id',
964+ type: 'string'
965+ }
966+ }
967+ });
968+
969+ cube('merchant_and_product_dims', {
970+ sql: \`
971+ SELECT 'M1' AS merchant_id, 'P1' AS product_id, 'Organic' AS acquisition_channel, 'SOLD' AS status
972+ UNION ALL
973+ SELECT 'M1' AS merchant_id, 'P2' AS product_id, 'Paid' AS acquisition_channel, 'PAID' AS status
974+ UNION ALL
975+ SELECT 'M2' AS merchant_id, 'P1' AS product_id, 'Referral' AS acquisition_channel, 'RETURNED' AS status
976+ \`,
977+
978+ dimensions: {
979+ product_id: {
980+ sql: 'product_id',
981+ type: 'string',
982+ primary_key: true
983+ },
984+ merchant_id: {
985+ sql: 'merchant_id',
986+ type: 'string',
987+ primary_key: true
988+ },
989+ status: {
990+ sql: 'status',
991+ type: 'string'
992+ },
993+ acquisition_channel: {
994+ sql: 'acquisition_channel',
995+ type: 'string'
996+ }
997+ },
998+
999+ pre_aggregations: {
1000+ bridge_rollup: {
1001+ dimensions: [
1002+ merchant_id,
1003+ product_id,
1004+ acquisition_channel,
1005+ status
1006+ ]
1007+ }
1008+ }
1009+ });
1010+
1011+ cube('other_facts', {
1012+ sql: \`
1013+ SELECT 1 AS id, 1 AS fact_id, 'OF1' AS fact
1014+ UNION ALL
1015+ SELECT 2 AS id, 2 AS fact_id, 'OF2' AS fact
1016+ UNION ALL
1017+ SELECT 3 AS id, 3 AS fact_id, 'OF3' AS fact
1018+ \`,
1019+
1020+ dimensions: {
1021+ other_fact_id: {
1022+ sql: 'id',
1023+ type: 'number',
1024+ primary_key: true
1025+ },
1026+ fact_id: {
1027+ sql: 'fact_id',
1028+ type: 'number'
1029+ },
1030+ fact: {
1031+ sql: 'fact',
1032+ type: 'string'
1033+ }
1034+ },
1035+
1036+ pre_aggregations: {
1037+ bridge_rollup: {
1038+ dimensions: [
1039+ fact_id,
1040+ fact
1041+ ]
1042+ }
1043+ }
1044+
1045+ });
1046+
1047+ cube('test_facts', {
1048+ sql: \`
1049+ SELECT 1 AS id, 101 AS merchant_sk, 201 AS product_sk, 100 AS amount
1050+ UNION ALL
1051+ SELECT 2 AS id, 101 AS merchant_sk, 202 AS product_sk, 150 AS amount
1052+ UNION ALL
1053+ SELECT 3 AS id, 102 AS merchant_sk, 201 AS product_sk, 200 AS amount
1054+ \`,
1055+
1056+ joins: {
1057+ merchant_dims: {
1058+ relationship: 'many_to_one',
1059+ sql: \`\${CUBE.merchant_sk} = \${merchant_dims.merchant_sk}\`
1060+ },
1061+ product_dims: {
1062+ relationship: 'many_to_one',
1063+ sql: \`\${CUBE.product_sk} = \${product_dims.product_sk}\`
1064+ },
1065+ // Transitive join - depends on merchant_dims and product_dims
1066+ merchant_and_product_dims: {
1067+ relationship: 'many_to_one',
1068+ sql: \`\${merchant_dims.merchant_id} = \${merchant_and_product_dims.merchant_id} AND \${product_dims.product_id} = \${merchant_and_product_dims.product_id}\`
1069+ },
1070+ other_facts: {
1071+ relationship: 'one_to_many',
1072+ sql: \`\${CUBE.id} = \${other_facts.fact_id}\`
1073+ },
1074+ },
1075+
1076+ dimensions: {
1077+ id: {
1078+ sql: 'id',
1079+ type: 'number',
1080+ primary_key: true
1081+ },
1082+ merchant_sk: {
1083+ sql: 'merchant_sk',
1084+ type: 'number'
1085+ },
1086+ product_sk: {
1087+ sql: 'product_sk',
1088+ type: 'number'
1089+ },
1090+ acquisition_channel: {
1091+ sql: \`\${merchant_and_product_dims.acquisition_channel}\`,
1092+ type: 'string'
1093+ }
1094+ },
1095+
1096+ measures: {
1097+ amount_sum: {
1098+ sql: 'amount',
1099+ type: 'sum'
1100+ }
1101+ },
1102+
1103+ pre_aggregations: {
1104+ facts_rollup: {
1105+ dimensions: [
1106+ id,
1107+ merchant_sk,
1108+ merchant_dims.merchant_sk,
1109+ merchant_dims.merchant_id,
1110+ merchant_and_product_dims.merchant_id,
1111+ product_sk,
1112+ product_dims.product_sk,
1113+ product_dims.product_id,
1114+ merchant_and_product_dims.product_id,
1115+ acquisition_channel,
1116+ merchant_and_product_dims.status
1117+ ]
1118+ },
1119+ rollupJoinTransitive: {
1120+ type: 'rollupJoin',
1121+ dimensions: [
1122+ merchant_sk,
1123+ product_sk,
1124+ merchant_and_product_dims.status,
1125+ other_facts.fact
1126+ ],
1127+ rollups: [
1128+ facts_rollup,
1129+ other_facts.bridge_rollup
1130+ ]
1131+ }
1132+ }
1133+ });
1134+
9281135 ` ) ;
9291136
9301137 it ( 'simple pre-aggregation' , async ( ) => {
@@ -3234,4 +3441,58 @@ describe('PreAggregations', () => {
32343441 } ) ;
32353442 } ) ;
32363443 }
3444+
3445+ it ( 'rollupJoin pre-aggregation matching with transitive joins' , async ( ) => {
3446+ await compiler . compile ( ) ;
3447+
3448+ const query = new PostgresQuery ( { joinGraph, cubeEvaluator, compiler } , {
3449+ dimensions : [
3450+ 'test_facts.merchant_sk' ,
3451+ 'test_facts.product_sk' ,
3452+ 'merchant_and_product_dims.status' ,
3453+ 'other_facts.fact'
3454+ ] ,
3455+ timezone : 'America/Los_Angeles' ,
3456+ preAggregationsSchema : ''
3457+ } ) ;
3458+
3459+ const queryAndParams = query . buildSqlAndParams ( ) ;
3460+ console . log ( queryAndParams ) ;
3461+ const preAggregationsDescription : any = query . preAggregations ?. preAggregationsDescription ( ) ;
3462+ console . log ( JSON . stringify ( preAggregationsDescription , null , 2 ) ) ;
3463+
3464+ // Verify that both rollups are included in the description
3465+ expect ( preAggregationsDescription . length ) . toBe ( 2 ) ;
3466+ const factsRollup = preAggregationsDescription . find ( p => p . preAggregationId === 'test_facts.facts_rollup' ) ;
3467+ const bridgeRollup = preAggregationsDescription . find ( p => p . preAggregationId === 'other_facts.bridge_rollup' ) ;
3468+ expect ( factsRollup ) . toBeDefined ( ) ;
3469+ expect ( bridgeRollup ) . toBeDefined ( ) ;
3470+
3471+ // Verify that the rollupJoin pre-aggregation can be used for the query
3472+ expect ( query . preAggregations ?. preAggregationForQuery ?. canUsePreAggregation ) . toEqual ( true ) ;
3473+ expect ( query . preAggregations ?. preAggregationForQuery ?. preAggregationName ) . toEqual ( 'rollupJoinTransitive' ) ;
3474+
3475+ return dbRunner . evaluateQueryWithPreAggregations ( query ) . then ( res => {
3476+ expect ( res ) . toEqual ( [
3477+ {
3478+ merchant_and_product_dims__status : 'SOLD' ,
3479+ other_facts__fact : 'OF1' ,
3480+ test_facts__merchant_sk : 101 ,
3481+ test_facts__product_sk : 201 ,
3482+ } ,
3483+ {
3484+ merchant_and_product_dims__status : 'PAID' ,
3485+ other_facts__fact : 'OF2' ,
3486+ test_facts__merchant_sk : 101 ,
3487+ test_facts__product_sk : 202 ,
3488+ } ,
3489+ {
3490+ merchant_and_product_dims__status : 'RETURNED' ,
3491+ other_facts__fact : 'OF3' ,
3492+ test_facts__merchant_sk : 102 ,
3493+ test_facts__product_sk : 201 ,
3494+ } ,
3495+ ] ) ;
3496+ } ) ;
3497+ } ) ;
32373498} ) ;
0 commit comments