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
@@ -1805,26 +1806,97 @@ def test_snapshot_triggers(init_and_plan_context: t.Callable, mocker: MockerFixt
18051806 context , plan = init_and_plan_context ("examples/sushi" )
18061807 context .apply (plan )
18071808
1809+ # modify 3 models
1810+ # - 2 breaking changes for testing plan directly modified triggers
1811+ # - 1 adding an auto-restatement for subsequent `run` test
1812+ marketing = context .get_model ("sushi.marketing" )
1813+ marketing_kwargs = {
1814+ ** marketing .dict (),
1815+ "query" : d .parse_one (
1816+ f"{ marketing .query .sql (dialect = 'duckdb' )} ORDER BY customer_id" , dialect = "duckdb"
1817+ ),
1818+ }
1819+ context .upsert_model (SqlModel .parse_obj (marketing_kwargs ))
1820+
1821+ customers = context .get_model ("sushi.customers" )
1822+ customers_kwargs = {
1823+ ** customers .dict (),
1824+ "query" : d .parse_one (
1825+ f"{ customers .query .sql (dialect = 'duckdb' )} ORDER BY customer_id" , dialect = "duckdb"
1826+ ),
1827+ }
1828+ context .upsert_model (SqlModel .parse_obj (customers_kwargs ))
1829+
18081830 # add auto restatement to orders
1809- model = context .get_model ("sushi.orders" )
1810- kind = {
1811- ** model .kind .dict (),
1831+ orders = context .get_model ("sushi.orders" )
1832+ orders_kind = {
1833+ ** orders .kind .dict (),
18121834 "auto_restatement_cron" : "@hourly" ,
18131835 }
1814- kwargs = {
1815- ** model .dict (),
1816- "kind" : kind ,
1836+ orders_kwargs = {
1837+ ** orders .dict (),
1838+ "kind" : orders_kind ,
18171839 }
1818- context .upsert_model (PythonModel .parse_obj (kwargs ))
1819- plan = context .plan_builder (skip_tests = True ).build ()
1820- context .apply (plan )
1840+ context .upsert_model (PythonModel .parse_obj (orders_kwargs ))
18211841
1822- # Mock run_merged_intervals to capture triggers arg
1823- scheduler = context .scheduler ()
1824- run_merged_intervals_mock = mocker .patch .object (
1825- scheduler , "run_merged_intervals" , return_value = ([], [])
1842+ spy = mocker .spy (sqlmesh .core .scheduler .Scheduler , "run_merged_intervals" )
1843+
1844+ context .plan (auto_apply = True , no_prompts = True , categorizer_config = CategorizerConfig .all_full ())
1845+
1846+ # PLAN: directly modified triggers
1847+ actual_triggers = spy .call_args .kwargs ["snapshot_evaluation_triggers" ]
1848+ actual_triggers_name = {
1849+ k .name : sorted ([s .name for s in v .directly_modified_triggers ])
1850+ for k , v in actual_triggers .items ()
1851+ if v .directly_modified_triggers
1852+ }
1853+ marketing_name = '"memory"."sushi"."marketing"'
1854+ customers_name = '"memory"."sushi"."customers"'
1855+ marketing_customers_names = sorted ([marketing_name , customers_name ])
1856+ children_names = [
1857+ f'"memory"."sushi"."{ model } "'
1858+ for model in {
1859+ "waiter_as_customer_by_day" ,
1860+ "active_customers" ,
1861+ "count_customers_active" ,
1862+ "count_customers_inactive" ,
1863+ }
1864+ ]
1865+ assert actual_triggers_name == {
1866+ marketing_name : [marketing_name ],
1867+ customers_name : [customers_name ],
1868+ ** {k : marketing_customers_names for k in children_names },
1869+ }
1870+
1871+ # PLAN: restatement triggers
1872+ spy .reset_mock ()
1873+ context .plan (
1874+ restate_models = [
1875+ '"memory"."sushi"."marketing"' ,
1876+ '"memory"."sushi"."order_items"' ,
1877+ '"memory"."sushi"."waiter_revenue_by_day"' ,
1878+ ],
1879+ auto_apply = True ,
1880+ no_prompts = True ,
18261881 )
18271882
1883+ order_items_name = '"memory"."sushi"."order_items"'
1884+ waiter_revenue_by_day_name = '"memory"."sushi"."waiter_revenue_by_day"'
1885+ actual_triggers = spy .call_args .kwargs ["snapshot_evaluation_triggers" ]
1886+ actual_triggers_name = {
1887+ k .name : sorted ([s .name for s in v .restatement_triggers ])
1888+ for k , v in actual_triggers .items ()
1889+ if v .restatement_triggers
1890+ }
1891+ assert actual_triggers_name == {
1892+ waiter_revenue_by_day_name : [waiter_revenue_by_day_name , order_items_name ],
1893+ order_items_name : [order_items_name ],
1894+ '"memory"."sushi"."top_waiters"' : [waiter_revenue_by_day_name ],
1895+ '"memory"."sushi"."customer_revenue_by_day"' : [order_items_name ],
1896+ '"memory"."sushi"."customer_revenue_lifetime"' : [order_items_name ],
1897+ }
1898+
1899+ # RUN: select and auto-restatement triggers
18281900 # User selects top_waiters and waiter_revenue_by_day, others added as auto-upstream
18291901 selected_models = {"top_waiters" , "waiter_revenue_by_day" }
18301902 selected_models_auto_upstream = {"order_items" , "orders" , "items" }
@@ -1835,6 +1907,11 @@ def test_snapshot_triggers(init_and_plan_context: t.Callable, mocker: MockerFixt
18351907 f'"memory"."sushi"."{ model } "' for model in selected_models
18361908 }
18371909
1910+ scheduler = context .scheduler ()
1911+ run_merged_intervals_mock = mocker .patch .object (
1912+ scheduler , "run_merged_intervals" , return_value = ([], [])
1913+ )
1914+
18381915 with time_machine .travel ("2023-01-09 00:00:01 UTC" ):
18391916 scheduler .run (
18401917 environment = c .PROD ,
0 commit comments