@@ -2937,6 +2937,141 @@ def test_migrate_rows(state_sync: EngineAdapterStateSync, mocker: MockerFixture)
29372937 )
29382938
29392939
2940+ def test_migrate_v0071_keeps_still_live_dev_intervals () -> None :
2941+ from sqlmesh .migrations import v0071_add_dev_version_to_intervals as migration
2942+
2943+ engine_adapter = create_engine_adapter (duckdb .connect , "duckdb" )
2944+ engine_adapter .create_schema (c .SQLMESH )
2945+
2946+ engine_adapter .create_table (
2947+ "sqlmesh._snapshots" ,
2948+ {
2949+ "name" : exp .DataType .build ("text" ),
2950+ "identifier" : exp .DataType .build ("text" ),
2951+ "version" : exp .DataType .build ("text" ),
2952+ "snapshot" : exp .DataType .build ("text" ),
2953+ "kind_name" : exp .DataType .build ("text" ),
2954+ "updated_ts" : exp .DataType .build ("bigint" ),
2955+ "unpaused_ts" : exp .DataType .build ("bigint" ),
2956+ "ttl_ms" : exp .DataType .build ("bigint" ),
2957+ "unrestorable" : exp .DataType .build ("boolean" ),
2958+ },
2959+ )
2960+ # The intervals table at schema version 70, before v0071 adds the dev_version column.
2961+ engine_adapter .create_table (
2962+ "sqlmesh._intervals" ,
2963+ {
2964+ "id" : exp .DataType .build ("text" ),
2965+ "created_ts" : exp .DataType .build ("bigint" ),
2966+ "name" : exp .DataType .build ("text" ),
2967+ "identifier" : exp .DataType .build ("text" ),
2968+ "version" : exp .DataType .build ("text" ),
2969+ "start_ts" : exp .DataType .build ("bigint" ),
2970+ "end_ts" : exp .DataType .build ("bigint" ),
2971+ "is_dev" : exp .DataType .build ("boolean" ),
2972+ "is_removed" : exp .DataType .build ("boolean" ),
2973+ "is_compacted" : exp .DataType .build ("boolean" ),
2974+ "is_pending_restatement" : exp .DataType .build ("boolean" ),
2975+ },
2976+ )
2977+
2978+ def snapshot_blob (name : str , identifier : str , version : str , dev_version : str ) -> str :
2979+ return json .dumps (
2980+ {
2981+ "name" : name ,
2982+ "identifier" : identifier ,
2983+ "version" : version ,
2984+ "dev_version" : dev_version ,
2985+ "fingerprint" : {
2986+ "data_hash" : "data" ,
2987+ "metadata_hash" : "metadata" ,
2988+ "parent_data_hash" : "parent_data" ,
2989+ "parent_metadata_hash" : "parent_metadata" ,
2990+ },
2991+ "previous_versions" : [],
2992+ }
2993+ )
2994+
2995+ engine_adapter .insert_append (
2996+ "sqlmesh._snapshots" ,
2997+ pd .DataFrame (
2998+ [
2999+ {
3000+ "name" : '"db"."prod_model"' ,
3001+ "identifier" : "prod_ident" ,
3002+ "version" : "prod_ver" ,
3003+ "snapshot" : snapshot_blob (
3004+ '"db"."prod_model"' , "prod_ident" , "prod_ver" , "prod_dev"
3005+ ),
3006+ "kind_name" : "FULL" ,
3007+ "updated_ts" : 1 ,
3008+ "unpaused_ts" : None ,
3009+ "ttl_ms" : None ,
3010+ "unrestorable" : False ,
3011+ },
3012+ {
3013+ "name" : '"db"."dev_model"' ,
3014+ "identifier" : "dev_ident" ,
3015+ "version" : "dev_ver" ,
3016+ "snapshot" : snapshot_blob (
3017+ '"db"."dev_model"' , "dev_ident" , "dev_ver" , "dev_dev"
3018+ ),
3019+ "kind_name" : "FULL" ,
3020+ "updated_ts" : 1 ,
3021+ "unpaused_ts" : None ,
3022+ "ttl_ms" : None ,
3023+ "unrestorable" : False ,
3024+ },
3025+ ]
3026+ ),
3027+ )
3028+
3029+ engine_adapter .insert_append (
3030+ "sqlmesh._intervals" ,
3031+ pd .DataFrame (
3032+ [
3033+ # A non-dev interval that is always kept; it forces the migration to
3034+ # rewrite the intervals table so a wrongly-dropped dev interval is
3035+ # actually deleted rather than left behind by the empty-result guard.
3036+ {
3037+ "id" : "prod_int" ,
3038+ "created_ts" : 1 ,
3039+ "name" : '"db"."prod_model"' ,
3040+ "identifier" : "prod_ident" ,
3041+ "version" : "prod_ver" ,
3042+ "start_ts" : 100 ,
3043+ "end_ts" : 200 ,
3044+ "is_dev" : False ,
3045+ "is_removed" : False ,
3046+ "is_compacted" : False ,
3047+ "is_pending_restatement" : False ,
3048+ },
3049+ # A dev interval whose version and dev version are both still live.
3050+ # It must be preserved by the migration.
3051+ {
3052+ "id" : "dev_int" ,
3053+ "created_ts" : 1 ,
3054+ "name" : '"db"."dev_model"' ,
3055+ "identifier" : "dev_ident" ,
3056+ "version" : "dev_ver" ,
3057+ "start_ts" : 100 ,
3058+ "end_ts" : 200 ,
3059+ "is_dev" : True ,
3060+ "is_removed" : False ,
3061+ "is_compacted" : False ,
3062+ "is_pending_restatement" : False ,
3063+ },
3064+ ]
3065+ ),
3066+ )
3067+
3068+ migration .migrate_schemas (engine_adapter , c .SQLMESH )
3069+ migration .migrate_rows (engine_adapter , c .SQLMESH )
3070+
3071+ surviving_ids = {row [0 ] for row in engine_adapter .fetchall ("SELECT id FROM sqlmesh._intervals" )}
3072+ assert surviving_ids == {"prod_int" , "dev_int" }
3073+
3074+
29403075def test_backup_state (state_sync : EngineAdapterStateSync , mocker : MockerFixture ) -> None :
29413076 state_sync .engine_adapter .replace_query (
29423077 "sqlmesh._snapshots" ,
0 commit comments