@@ -703,3 +703,118 @@ def test_retention_metric_level_filters():
703703 # Day 1: only user 1 active -> 50%
704704 day1 = [r for r in rows if r [1 ] == 1 ]
705705 assert day1 [0 ][4 ] == 50.0
706+
707+
708+ def test_retention_multiple_retention_metrics_raises ():
709+ """Test that querying two retention metrics raises ValueError."""
710+ events = Model (
711+ name = "events" ,
712+ sql = "SELECT 1 AS uid, 'signup' AS event, '2024-01-01'::DATE AS ts" ,
713+ primary_key = "uid" ,
714+ dimensions = [
715+ Dimension (name = "uid" , sql = "uid" , type = "categorical" ),
716+ Dimension (name = "event" , sql = "event" , type = "categorical" ),
717+ Dimension (name = "ts" , sql = "ts" , type = "time" ),
718+ ],
719+ metrics = [],
720+ )
721+
722+ ret1 = Metric (
723+ name = "retention_a" ,
724+ type = "retention" ,
725+ entity = "uid" ,
726+ cohort_event = "event = 'signup'" ,
727+ periods = 1 ,
728+ )
729+ ret2 = Metric (
730+ name = "retention_b" ,
731+ type = "retention" ,
732+ entity = "uid" ,
733+ cohort_event = "event = 'signup'" ,
734+ periods = 2 ,
735+ )
736+
737+ graph = SemanticGraph ()
738+ graph .add_model (events )
739+ graph .add_metric (ret1 )
740+ graph .add_metric (ret2 )
741+
742+ generator = SQLGenerator (graph )
743+ with pytest .raises (ValueError , match = "Only one retention metric can be queried at a time" ):
744+ generator .generate (metrics = ["retention_a" , "retention_b" ], dimensions = [])
745+
746+
747+ def test_retention_mixed_with_regular_metric_raises ():
748+ """Test that mixing retention + regular metric raises ValueError."""
749+ events = Model (
750+ name = "events" ,
751+ sql = "SELECT 1 AS uid, 'signup' AS event, '2024-01-01'::DATE AS ts, 10 AS revenue" ,
752+ primary_key = "uid" ,
753+ dimensions = [
754+ Dimension (name = "uid" , sql = "uid" , type = "categorical" ),
755+ Dimension (name = "event" , sql = "event" , type = "categorical" ),
756+ Dimension (name = "ts" , sql = "ts" , type = "time" ),
757+ ],
758+ metrics = [
759+ Metric (name = "total_revenue" , agg = "sum" , sql = "revenue" ),
760+ ],
761+ )
762+
763+ retention = Metric (
764+ name = "retention" ,
765+ type = "retention" ,
766+ entity = "uid" ,
767+ cohort_event = "event = 'signup'" ,
768+ periods = 1 ,
769+ )
770+
771+ graph = SemanticGraph ()
772+ graph .add_model (events )
773+ graph .add_metric (retention )
774+
775+ generator = SQLGenerator (graph )
776+ with pytest .raises (ValueError , match = "Retention metrics cannot be combined with other metrics" ):
777+ generator .generate (metrics = ["retention" , "events.total_revenue" ], dimensions = [])
778+
779+
780+ def test_retention_offset_in_sql ():
781+ """Test that offset parameter is included in retention SQL output."""
782+ events = Model (
783+ name = "events" ,
784+ sql = """
785+ SELECT 1 AS uid, 'signup' AS event, '2024-01-01'::DATE AS ts
786+ UNION ALL SELECT 1, 'login', '2024-01-02'::DATE
787+ UNION ALL SELECT 2, 'signup', '2024-01-01'::DATE
788+ UNION ALL SELECT 2, 'login', '2024-01-01'::DATE
789+ """ ,
790+ primary_key = "uid" ,
791+ dimensions = [
792+ Dimension (name = "uid" , sql = "uid" , type = "categorical" ),
793+ Dimension (name = "event" , sql = "event" , type = "categorical" ),
794+ Dimension (name = "ts" , sql = "ts" , type = "time" ),
795+ ],
796+ metrics = [],
797+ )
798+
799+ retention = Metric (
800+ name = "retention" ,
801+ type = "retention" ,
802+ entity = "uid" ,
803+ cohort_event = "event = 'signup'" ,
804+ periods = 2 ,
805+ retention_granularity = "day" ,
806+ )
807+
808+ graph = SemanticGraph ()
809+ graph .add_model (events )
810+ graph .add_metric (retention )
811+
812+ generator = SQLGenerator (graph )
813+ sql = generator .generate (metrics = ["retention" ], dimensions = [], limit = 5 , offset = 10 )
814+
815+ assert "LIMIT 5" in sql
816+ assert "OFFSET 10" in sql
817+
818+ # Without offset, OFFSET should not appear
819+ sql_no_offset = generator .generate (metrics = ["retention" ], dimensions = [], limit = 5 )
820+ assert "OFFSET" not in sql_no_offset
0 commit comments