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
@@ -1867,26 +1868,97 @@ def test_snapshot_triggers(init_and_plan_context: t.Callable, mocker: MockerFixt
18671868 context , plan = init_and_plan_context ("examples/sushi" )
18681869 context .apply (plan )
18691870
1871+ # modify 3 models
1872+ # - 2 breaking changes for testing plan directly modified triggers
1873+ # - 1 adding an auto-restatement for subsequent `run` test
1874+ marketing = context .get_model ("sushi.marketing" )
1875+ marketing_kwargs = {
1876+ ** marketing .dict (),
1877+ "query" : d .parse_one (
1878+ f"{ marketing .query .sql (dialect = 'duckdb' )} ORDER BY customer_id" , dialect = "duckdb"
1879+ ),
1880+ }
1881+ context .upsert_model (SqlModel .parse_obj (marketing_kwargs ))
1882+
1883+ customers = context .get_model ("sushi.customers" )
1884+ customers_kwargs = {
1885+ ** customers .dict (),
1886+ "query" : d .parse_one (
1887+ f"{ customers .query .sql (dialect = 'duckdb' )} ORDER BY customer_id" , dialect = "duckdb"
1888+ ),
1889+ }
1890+ context .upsert_model (SqlModel .parse_obj (customers_kwargs ))
1891+
18701892 # add auto restatement to orders
1871- model = context .get_model ("sushi.orders" )
1872- kind = {
1873- ** model .kind .dict (),
1893+ orders = context .get_model ("sushi.orders" )
1894+ orders_kind = {
1895+ ** orders .kind .dict (),
18741896 "auto_restatement_cron" : "@hourly" ,
18751897 }
1876- kwargs = {
1877- ** model .dict (),
1878- "kind" : kind ,
1898+ orders_kwargs = {
1899+ ** orders .dict (),
1900+ "kind" : orders_kind ,
18791901 }
1880- context .upsert_model (PythonModel .parse_obj (kwargs ))
1881- plan = context .plan_builder (skip_tests = True ).build ()
1882- context .apply (plan )
1902+ context .upsert_model (PythonModel .parse_obj (orders_kwargs ))
18831903
1884- # Mock run_merged_intervals to capture triggers arg
1885- scheduler = context .scheduler ()
1886- run_merged_intervals_mock = mocker .patch .object (
1887- scheduler , "run_merged_intervals" , return_value = ([], [])
1904+ spy = mocker .spy (sqlmesh .core .scheduler .Scheduler , "run_merged_intervals" )
1905+
1906+ context .plan (auto_apply = True , no_prompts = True , categorizer_config = CategorizerConfig .all_full ())
1907+
1908+ # PLAN: directly modified triggers
1909+ actual_triggers = spy .call_args .kwargs ["snapshot_evaluation_triggers" ]
1910+ actual_triggers_name = {
1911+ k .name : sorted ([s .name for s in v .directly_modified_triggers ])
1912+ for k , v in actual_triggers .items ()
1913+ if v .directly_modified_triggers
1914+ }
1915+ marketing_name = '"memory"."sushi"."marketing"'
1916+ customers_name = '"memory"."sushi"."customers"'
1917+ marketing_customers_names = sorted ([marketing_name , customers_name ])
1918+ children_names = [
1919+ f'"memory"."sushi"."{ model } "'
1920+ for model in {
1921+ "waiter_as_customer_by_day" ,
1922+ "active_customers" ,
1923+ "count_customers_active" ,
1924+ "count_customers_inactive" ,
1925+ }
1926+ ]
1927+ assert actual_triggers_name == {
1928+ marketing_name : [marketing_name ],
1929+ customers_name : [customers_name ],
1930+ ** {k : marketing_customers_names for k in children_names },
1931+ }
1932+
1933+ # PLAN: restatement triggers
1934+ spy .reset_mock ()
1935+ context .plan (
1936+ restate_models = [
1937+ '"memory"."sushi"."marketing"' ,
1938+ '"memory"."sushi"."order_items"' ,
1939+ '"memory"."sushi"."waiter_revenue_by_day"' ,
1940+ ],
1941+ auto_apply = True ,
1942+ no_prompts = True ,
18881943 )
18891944
1945+ order_items_name = '"memory"."sushi"."order_items"'
1946+ waiter_revenue_by_day_name = '"memory"."sushi"."waiter_revenue_by_day"'
1947+ actual_triggers = spy .call_args .kwargs ["snapshot_evaluation_triggers" ]
1948+ actual_triggers_name = {
1949+ k .name : sorted ([s .name for s in v .restatement_triggers ])
1950+ for k , v in actual_triggers .items ()
1951+ if v .restatement_triggers
1952+ }
1953+ assert actual_triggers_name == {
1954+ waiter_revenue_by_day_name : [waiter_revenue_by_day_name , order_items_name ],
1955+ order_items_name : [order_items_name ],
1956+ '"memory"."sushi"."top_waiters"' : [waiter_revenue_by_day_name ],
1957+ '"memory"."sushi"."customer_revenue_by_day"' : [order_items_name ],
1958+ '"memory"."sushi"."customer_revenue_lifetime"' : [order_items_name ],
1959+ }
1960+
1961+ # RUN: select and auto-restatement triggers
18901962 # User selects top_waiters and waiter_revenue_by_day, others added as auto-upstream
18911963 selected_models = {"top_waiters" , "waiter_revenue_by_day" }
18921964 selected_models_auto_upstream = {"order_items" , "orders" , "items" }
@@ -1897,6 +1969,11 @@ def test_snapshot_triggers(init_and_plan_context: t.Callable, mocker: MockerFixt
18971969 f'"memory"."sushi"."{ model } "' for model in selected_models
18981970 }
18991971
1972+ scheduler = context .scheduler ()
1973+ run_merged_intervals_mock = mocker .patch .object (
1974+ scheduler , "run_merged_intervals" , return_value = ([], [])
1975+ )
1976+
19001977 with time_machine .travel ("2023-01-09 00:00:01 UTC" ):
19011978 scheduler .run (
19021979 environment = c .PROD ,
0 commit comments