2626
2727
2828from sqlmesh import CustomMaterialization
29+ import sqlmesh
2930from sqlmesh .cli .project_init import init_example_project
3031from sqlmesh .core import constants as c
3132from sqlmesh .core import dialect as d
@@ -1813,26 +1814,97 @@ def test_snapshot_triggers(init_and_plan_context: t.Callable, mocker: MockerFixt
18131814 context , plan = init_and_plan_context ("examples/sushi" )
18141815 context .apply (plan )
18151816
1817+ # modify 3 models
1818+ # - 2 breaking changes for testing plan directly modified triggers
1819+ # - 1 adding an auto-restatement for subsequent `run` test
1820+ marketing = context .get_model ("sushi.marketing" )
1821+ marketing_kwargs = {
1822+ ** marketing .dict (),
1823+ "query" : d .parse_one (
1824+ f"{ marketing .query .sql (dialect = 'duckdb' )} ORDER BY customer_id" , dialect = "duckdb"
1825+ ),
1826+ }
1827+ context .upsert_model (SqlModel .parse_obj (marketing_kwargs ))
1828+
1829+ customers = context .get_model ("sushi.customers" )
1830+ customers_kwargs = {
1831+ ** customers .dict (),
1832+ "query" : d .parse_one (
1833+ f"{ customers .query .sql (dialect = 'duckdb' )} ORDER BY customer_id" , dialect = "duckdb"
1834+ ),
1835+ }
1836+ context .upsert_model (SqlModel .parse_obj (customers_kwargs ))
1837+
18161838 # add auto restatement to orders
1817- model = context .get_model ("sushi.orders" )
1818- kind = {
1819- ** model .kind .dict (),
1839+ orders = context .get_model ("sushi.orders" )
1840+ orders_kind = {
1841+ ** orders .kind .dict (),
18201842 "auto_restatement_cron" : "@hourly" ,
18211843 }
1822- kwargs = {
1823- ** model .dict (),
1824- "kind" : kind ,
1844+ orders_kwargs = {
1845+ ** orders .dict (),
1846+ "kind" : orders_kind ,
18251847 }
1826- context .upsert_model (PythonModel .parse_obj (kwargs ))
1827- plan = context .plan_builder (skip_tests = True ).build ()
1828- context .apply (plan )
1848+ context .upsert_model (PythonModel .parse_obj (orders_kwargs ))
18291849
1830- # Mock run_merged_intervals to capture triggers arg
1831- scheduler = context .scheduler ()
1832- run_merged_intervals_mock = mocker .patch .object (
1833- scheduler , "run_merged_intervals" , return_value = ([], [])
1850+ spy = mocker .spy (sqlmesh .core .scheduler .Scheduler , "run_merged_intervals" )
1851+
1852+ context .plan (auto_apply = True , no_prompts = True , categorizer_config = CategorizerConfig .all_full ())
1853+
1854+ # PLAN: directly modified triggers
1855+ actual_triggers = spy .call_args .kwargs ["snapshot_evaluation_triggers" ]
1856+ actual_triggers_name = {
1857+ k .name : sorted ([s .name for s in v .directly_modified_triggers ])
1858+ for k , v in actual_triggers .items ()
1859+ if v .directly_modified_triggers
1860+ }
1861+ marketing_name = '"memory"."sushi"."marketing"'
1862+ customers_name = '"memory"."sushi"."customers"'
1863+ marketing_customers_names = sorted ([marketing_name , customers_name ])
1864+ children_names = [
1865+ f'"memory"."sushi"."{ model } "'
1866+ for model in {
1867+ "waiter_as_customer_by_day" ,
1868+ "active_customers" ,
1869+ "count_customers_active" ,
1870+ "count_customers_inactive" ,
1871+ }
1872+ ]
1873+ assert actual_triggers_name == {
1874+ marketing_name : [marketing_name ],
1875+ customers_name : [customers_name ],
1876+ ** {k : marketing_customers_names for k in children_names },
1877+ }
1878+
1879+ # PLAN: restatement triggers
1880+ spy .reset_mock ()
1881+ context .plan (
1882+ restate_models = [
1883+ '"memory"."sushi"."marketing"' ,
1884+ '"memory"."sushi"."order_items"' ,
1885+ '"memory"."sushi"."waiter_revenue_by_day"' ,
1886+ ],
1887+ auto_apply = True ,
1888+ no_prompts = True ,
18341889 )
18351890
1891+ order_items_name = '"memory"."sushi"."order_items"'
1892+ waiter_revenue_by_day_name = '"memory"."sushi"."waiter_revenue_by_day"'
1893+ actual_triggers = spy .call_args .kwargs ["snapshot_evaluation_triggers" ]
1894+ actual_triggers_name = {
1895+ k .name : sorted ([s .name for s in v .restatement_triggers ])
1896+ for k , v in actual_triggers .items ()
1897+ if v .restatement_triggers
1898+ }
1899+ assert actual_triggers_name == {
1900+ waiter_revenue_by_day_name : [waiter_revenue_by_day_name , order_items_name ],
1901+ order_items_name : [order_items_name ],
1902+ '"memory"."sushi"."top_waiters"' : [waiter_revenue_by_day_name ],
1903+ '"memory"."sushi"."customer_revenue_by_day"' : [order_items_name ],
1904+ '"memory"."sushi"."customer_revenue_lifetime"' : [order_items_name ],
1905+ }
1906+
1907+ # RUN: select and auto-restatement triggers
18361908 # User selects top_waiters and waiter_revenue_by_day, others added as auto-upstream
18371909 selected_models = {"top_waiters" , "waiter_revenue_by_day" }
18381910 selected_models_auto_upstream = {"order_items" , "orders" , "items" }
@@ -1843,6 +1915,11 @@ def test_snapshot_triggers(init_and_plan_context: t.Callable, mocker: MockerFixt
18431915 f'"memory"."sushi"."{ model } "' for model in selected_models
18441916 }
18451917
1918+ scheduler = context .scheduler ()
1919+ run_merged_intervals_mock = mocker .patch .object (
1920+ scheduler , "run_merged_intervals" , return_value = ([], [])
1921+ )
1922+
18461923 with time_machine .travel ("2023-01-09 00:00:01 UTC" ):
18471924 scheduler .run (
18481925 environment = c .PROD ,
0 commit comments