@@ -819,6 +819,31 @@ impl<'a> SqlGenerator<'a> {
819819 }
820820
821821 fn graph_metric_owner_models ( & self , reference : & str , metric : & Metric ) -> Result < Vec < String > > {
822+ let mut visiting = HashSet :: new ( ) ;
823+ self . graph_metric_owner_models_inner ( reference, metric, & mut visiting)
824+ }
825+
826+ fn graph_metric_owner_models_inner (
827+ & self ,
828+ reference : & str ,
829+ metric : & Metric ,
830+ visiting : & mut HashSet < String > ,
831+ ) -> Result < Vec < String > > {
832+ if !visiting. insert ( reference. to_string ( ) ) {
833+ return Ok ( Vec :: new ( ) ) ;
834+ }
835+
836+ let result = self . graph_metric_owner_models_uncycled ( reference, metric, visiting) ;
837+ visiting. remove ( reference) ;
838+ result
839+ }
840+
841+ fn graph_metric_owner_models_uncycled (
842+ & self ,
843+ reference : & str ,
844+ metric : & Metric ,
845+ visiting : & mut HashSet < String > ,
846+ ) -> Result < Vec < String > > {
822847 let mut owners = HashSet :: new ( ) ;
823848
824849 for fragment in [
@@ -838,6 +863,13 @@ impl<'a> SqlGenerator<'a> {
838863 {
839864 self . collect_owner_models_from_fragment ( fragment, & mut owners) ;
840865 }
866+ for fragment in self . graph_metric_dependency_fragments ( metric) {
867+ self . collect_owner_models_from_graph_metric_dependencies (
868+ fragment,
869+ & mut owners,
870+ visiting,
871+ ) ?;
872+ }
841873
842874 for filter in & metric. filters {
843875 self . collect_owner_models_from_fragment ( filter, & mut owners) ;
@@ -890,6 +922,23 @@ impl<'a> SqlGenerator<'a> {
890922 Ok ( owners)
891923 }
892924
925+ fn graph_metric_dependency_fragments < ' b > ( & self , metric : & ' b Metric ) -> Vec < & ' b str > {
926+ match metric. r#type {
927+ MetricType :: Derived => metric. sql . iter ( ) . map ( String :: as_str) . collect ( ) ,
928+ MetricType :: Ratio => [ metric. numerator . as_deref ( ) , metric. denominator . as_deref ( ) ]
929+ . into_iter ( )
930+ . flatten ( )
931+ . collect ( ) ,
932+ MetricType :: Cumulative | MetricType :: TimeComparison => {
933+ [ metric. base_metric . as_deref ( ) , metric. sql . as_deref ( ) ]
934+ . into_iter ( )
935+ . flatten ( )
936+ . collect ( )
937+ }
938+ _ => Vec :: new ( ) ,
939+ }
940+ }
941+
893942 fn collect_owner_models_from_fragment ( & self , fragment : & str , owners : & mut HashSet < String > ) {
894943 let model_ref_re =
895944 regex:: Regex :: new ( r"\b([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)\b" )
@@ -905,6 +954,34 @@ impl<'a> SqlGenerator<'a> {
905954 }
906955 }
907956
957+ fn collect_owner_models_from_graph_metric_dependencies (
958+ & self ,
959+ fragment : & str ,
960+ owners : & mut HashSet < String > ,
961+ visiting : & mut HashSet < String > ,
962+ ) -> Result < ( ) > {
963+ let metric_ref_re = regex:: Regex :: new (
964+ r"\b([A-Za-z_][A-Za-z0-9_]*\.[A-Za-z_][A-Za-z0-9_]*|[A-Za-z_][A-Za-z0-9_]*)\b" ,
965+ )
966+ . expect ( "valid metric reference regex" ) ;
967+ for cap in metric_ref_re. captures_iter ( fragment) {
968+ let Some ( token_match) = cap. get ( 1 ) else {
969+ continue ;
970+ } ;
971+ let token = token_match. as_str ( ) ;
972+ if token. contains ( '.' ) || Self :: is_sql_keyword_or_function ( token) {
973+ continue ;
974+ }
975+ let Some ( metric) = self . graph . get_metric ( token) else {
976+ continue ;
977+ } ;
978+ for owner in self . graph_metric_owner_models_inner ( token, metric, visiting) ? {
979+ owners. insert ( owner) ;
980+ }
981+ }
982+ Ok ( ( ) )
983+ }
984+
908985 fn metric_for_ref ( & self , metric_ref : & MetricRef ) -> Result < & Metric > {
909986 self . metric_for_model_with_source (
910987 & metric_ref. model ,
@@ -4685,6 +4762,36 @@ mod tests {
46854762 assert ! ( !sql. contains( "FROM orders" ) , "{sql}" ) ;
46864763 }
46874764
4765+ #[ test]
4766+ fn test_graph_metric_owner_follows_unqualified_graph_metric_dependencies ( ) {
4767+ let mut graph = create_test_graph ( ) ;
4768+ graph
4769+ . add_metric ( Metric :: sum ( "signups" , "orders.signups" ) )
4770+ . unwrap ( ) ;
4771+ graph
4772+ . add_metric ( Metric :: sum ( "visitors" , "orders.visitors" ) )
4773+ . unwrap ( ) ;
4774+ graph
4775+ . add_metric ( Metric :: ratio ( "conversion_rate" , "signups" , "visitors" ) )
4776+ . unwrap ( ) ;
4777+ let generator = SqlGenerator :: new ( & graph) ;
4778+
4779+ let refs = generator
4780+ . parse_metric_refs ( & [ "conversion_rate" . to_string ( ) ] )
4781+ . unwrap ( ) ;
4782+
4783+ assert_eq ! ( refs. len( ) , 1 ) ;
4784+ assert_eq ! ( refs[ 0 ] . model, "orders" ) ;
4785+ assert_eq ! ( refs[ 0 ] . name, "conversion_rate" ) ;
4786+ assert ! ( refs[ 0 ] . graph_metric) ;
4787+
4788+ let query = SemanticQuery :: new ( ) . with_metrics ( vec ! [ "conversion_rate" . into( ) ] ) ;
4789+ let sql = generator. generate ( & query) . unwrap ( ) ;
4790+
4791+ assert ! ( sql. contains( "orders.signups AS signups_raw" ) , "{sql}" ) ;
4792+ assert ! ( sql. contains( "orders.visitors AS visitors_raw" ) , "{sql}" ) ;
4793+ }
4794+
46884795 #[ test]
46894796 fn test_qualified_model_metric_wins_over_same_name_graph_metric ( ) {
46904797 let mut graph = create_test_graph ( ) ;
0 commit comments