@@ -818,3 +818,53 @@ def test_retention_offset_in_sql():
818818 # Without offset, OFFSET should not appear
819819 sql_no_offset = generator .generate (metrics = ["retention" ], dimensions = [], limit = 5 )
820820 assert "OFFSET" not in sql_no_offset
821+
822+
823+ def test_retention_ambiguous_model_raises ():
824+ """Test that graph-level retention metric with entity in multiple models raises ValueError."""
825+ orders = Model (
826+ name = "orders" ,
827+ sql = "SELECT 1 AS user_id, 'purchase' AS event, '2024-01-01'::DATE AS ts" ,
828+ primary_key = "user_id" ,
829+ dimensions = [
830+ Dimension (name = "user_id" , sql = "user_id" , type = "categorical" ),
831+ Dimension (name = "event" , sql = "event" , type = "categorical" ),
832+ Dimension (name = "ts" , sql = "ts" , type = "time" ),
833+ ],
834+ metrics = [],
835+ )
836+
837+ sessions = Model (
838+ name = "sessions" ,
839+ sql = "SELECT 1 AS user_id, 'visit' AS event, '2024-01-01'::DATE AS ts" ,
840+ primary_key = "user_id" ,
841+ dimensions = [
842+ Dimension (name = "user_id" , sql = "user_id" , type = "categorical" ),
843+ Dimension (name = "event" , sql = "event" , type = "categorical" ),
844+ Dimension (name = "ts" , sql = "ts" , type = "time" ),
845+ ],
846+ metrics = [],
847+ )
848+
849+ retention = Metric (
850+ name = "retention" ,
851+ type = "retention" ,
852+ entity = "user_id" ,
853+ cohort_event = "event = 'purchase'" ,
854+ periods = 7 ,
855+ retention_granularity = "day" ,
856+ )
857+
858+ graph = SemanticGraph ()
859+ graph .add_model (orders )
860+ graph .add_model (sessions )
861+ graph .add_metric (retention )
862+
863+ generator = SQLGenerator (graph )
864+ with pytest .raises (ValueError , match = "Ambiguous model for retention metric" ) as exc_info :
865+ generator .generate (metrics = ["retention" ], dimensions = [])
866+
867+ # Verify error mentions both model names
868+ err_msg = str (exc_info .value )
869+ assert "orders" in err_msg
870+ assert "sessions" in err_msg
0 commit comments