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
@@ -1859,26 +1860,97 @@ def test_snapshot_triggers(init_and_plan_context: t.Callable, mocker: MockerFixt
18591860 context , plan = init_and_plan_context ("examples/sushi" )
18601861 context .apply (plan )
18611862
1863+ # modify 3 models
1864+ # - 2 breaking changes for testing plan directly modified triggers
1865+ # - 1 adding an auto-restatement for subsequent `run` test
1866+ marketing = context .get_model ("sushi.marketing" )
1867+ marketing_kwargs = {
1868+ ** marketing .dict (),
1869+ "query" : d .parse_one (
1870+ f"{ marketing .query .sql (dialect = 'duckdb' )} ORDER BY customer_id" , dialect = "duckdb"
1871+ ),
1872+ }
1873+ context .upsert_model (SqlModel .parse_obj (marketing_kwargs ))
1874+
1875+ customers = context .get_model ("sushi.customers" )
1876+ customers_kwargs = {
1877+ ** customers .dict (),
1878+ "query" : d .parse_one (
1879+ f"{ customers .query .sql (dialect = 'duckdb' )} ORDER BY customer_id" , dialect = "duckdb"
1880+ ),
1881+ }
1882+ context .upsert_model (SqlModel .parse_obj (customers_kwargs ))
1883+
18621884 # add auto restatement to orders
1863- model = context .get_model ("sushi.orders" )
1864- kind = {
1865- ** model .kind .dict (),
1885+ orders = context .get_model ("sushi.orders" )
1886+ orders_kind = {
1887+ ** orders .kind .dict (),
18661888 "auto_restatement_cron" : "@hourly" ,
18671889 }
1868- kwargs = {
1869- ** model .dict (),
1870- "kind" : kind ,
1890+ orders_kwargs = {
1891+ ** orders .dict (),
1892+ "kind" : orders_kind ,
18711893 }
1872- context .upsert_model (PythonModel .parse_obj (kwargs ))
1873- plan = context .plan_builder (skip_tests = True ).build ()
1874- context .apply (plan )
1894+ context .upsert_model (PythonModel .parse_obj (orders_kwargs ))
18751895
1876- # Mock run_merged_intervals to capture triggers arg
1877- scheduler = context .scheduler ()
1878- run_merged_intervals_mock = mocker .patch .object (
1879- scheduler , "run_merged_intervals" , return_value = ([], [])
1896+ spy = mocker .spy (sqlmesh .core .scheduler .Scheduler , "run_merged_intervals" )
1897+
1898+ context .plan (auto_apply = True , no_prompts = True , categorizer_config = CategorizerConfig .all_full ())
1899+
1900+ # PLAN: directly modified triggers
1901+ actual_triggers = spy .call_args .kwargs ["snapshot_evaluation_triggers" ]
1902+ actual_triggers_name = {
1903+ k .name : sorted ([s .name for s in v .directly_modified_triggers ])
1904+ for k , v in actual_triggers .items ()
1905+ if v .directly_modified_triggers
1906+ }
1907+ marketing_name = '"memory"."sushi"."marketing"'
1908+ customers_name = '"memory"."sushi"."customers"'
1909+ marketing_customers_names = sorted ([marketing_name , customers_name ])
1910+ children_names = [
1911+ f'"memory"."sushi"."{ model } "'
1912+ for model in {
1913+ "waiter_as_customer_by_day" ,
1914+ "active_customers" ,
1915+ "count_customers_active" ,
1916+ "count_customers_inactive" ,
1917+ }
1918+ ]
1919+ assert actual_triggers_name == {
1920+ marketing_name : [marketing_name ],
1921+ customers_name : [customers_name ],
1922+ ** {k : marketing_customers_names for k in children_names },
1923+ }
1924+
1925+ # PLAN: restatement triggers
1926+ spy .reset_mock ()
1927+ context .plan (
1928+ restate_models = [
1929+ '"memory"."sushi"."marketing"' ,
1930+ '"memory"."sushi"."order_items"' ,
1931+ '"memory"."sushi"."waiter_revenue_by_day"' ,
1932+ ],
1933+ auto_apply = True ,
1934+ no_prompts = True ,
18801935 )
18811936
1937+ order_items_name = '"memory"."sushi"."order_items"'
1938+ waiter_revenue_by_day_name = '"memory"."sushi"."waiter_revenue_by_day"'
1939+ actual_triggers = spy .call_args .kwargs ["snapshot_evaluation_triggers" ]
1940+ actual_triggers_name = {
1941+ k .name : sorted ([s .name for s in v .restatement_triggers ])
1942+ for k , v in actual_triggers .items ()
1943+ if v .restatement_triggers
1944+ }
1945+ assert actual_triggers_name == {
1946+ waiter_revenue_by_day_name : [waiter_revenue_by_day_name , order_items_name ],
1947+ order_items_name : [order_items_name ],
1948+ '"memory"."sushi"."top_waiters"' : [waiter_revenue_by_day_name ],
1949+ '"memory"."sushi"."customer_revenue_by_day"' : [order_items_name ],
1950+ '"memory"."sushi"."customer_revenue_lifetime"' : [order_items_name ],
1951+ }
1952+
1953+ # RUN: select and auto-restatement triggers
18821954 # User selects top_waiters and waiter_revenue_by_day, others added as auto-upstream
18831955 selected_models = {"top_waiters" , "waiter_revenue_by_day" }
18841956 selected_models_auto_upstream = {"order_items" , "orders" , "items" }
@@ -1889,6 +1961,11 @@ def test_snapshot_triggers(init_and_plan_context: t.Callable, mocker: MockerFixt
18891961 f'"memory"."sushi"."{ model } "' for model in selected_models
18901962 }
18911963
1964+ scheduler = context .scheduler ()
1965+ run_merged_intervals_mock = mocker .patch .object (
1966+ scheduler , "run_merged_intervals" , return_value = ([], [])
1967+ )
1968+
18921969 with time_machine .travel ("2023-01-09 00:00:01 UTC" ):
18931970 scheduler .run (
18941971 environment = c .PROD ,
0 commit comments