|
9 | 9 | from sqlglot import expressions as exp |
10 | 10 | from sqlglot import parse_one |
11 | 11 |
|
| 12 | +from pathlib import Path |
| 13 | +from sqlmesh import model |
12 | 14 | from sqlmesh.core.engine_adapter.mssql import MSSQLEngineAdapter |
13 | | -from sqlmesh.core.snapshot import SnapshotEvaluator, SnapshotChangeCategory |
| 15 | +from sqlmesh.core.snapshot import SnapshotEvaluator, SnapshotChangeCategory, Snapshot |
14 | 16 | from sqlmesh.core.model import load_sql_based_model |
| 17 | +from sqlmesh.core.model.kind import SCDType2ByTimeKind |
15 | 18 | from sqlmesh.core import dialect as d |
16 | | -from sqlmesh.core.engine_adapter.shared import ( |
17 | | - DataObject, |
18 | | - DataObjectType, |
19 | | -) |
| 19 | +from sqlmesh.core.engine_adapter.shared import DataObject, DataObjectType, SourceQuery |
20 | 20 | from sqlmesh.utils.date import to_ds |
21 | 21 | from tests.core.engine_adapter import to_sql_calls |
22 | 22 |
|
@@ -916,3 +916,89 @@ def test_replace_query_strategy(adapter: MSSQLEngineAdapter, mocker: MockerFixtu |
916 | 916 | "TRUNCATE TABLE [test_table];", |
917 | 917 | "INSERT INTO [test_table] ([a], [b]) SELECT [a] AS [a], [b] AS [b] FROM [db].[upstream_table] AS [upstream_table];", |
918 | 918 | ] |
| 919 | + |
| 920 | + |
| 921 | +def test_mssql_merge_exists_switches_strategy_from_truncate_to_merge( |
| 922 | + make_mocked_engine_adapter: t.Callable, mocker: MockerFixture |
| 923 | +): |
| 924 | + adapter = make_mocked_engine_adapter(MSSQLEngineAdapter) |
| 925 | + |
| 926 | + query = exp.select("*").from_("source") |
| 927 | + source_queries = [SourceQuery(query_factory=lambda: query)] |
| 928 | + |
| 929 | + # Test WITHOUT mssql_merge_exists, should use DELETE+INSERT strategy |
| 930 | + base_insert_overwrite = mocker.patch( |
| 931 | + "sqlmesh.core.engine_adapter.base.EngineAdapter._insert_overwrite_by_condition" |
| 932 | + ) |
| 933 | + |
| 934 | + adapter._insert_overwrite_by_condition( |
| 935 | + table_name="target", |
| 936 | + source_queries=source_queries, |
| 937 | + target_columns_to_types={ |
| 938 | + "id": exp.DataType.build("INT"), |
| 939 | + "value": exp.DataType.build("VARCHAR"), |
| 940 | + }, |
| 941 | + where=None, |
| 942 | + ) |
| 943 | + |
| 944 | + # Should call base DELETE+INSERT strategy |
| 945 | + assert base_insert_overwrite.called |
| 946 | + base_insert_overwrite.reset_mock() |
| 947 | + |
| 948 | + # Test WITH mssql_merge_exists uses MERGE strategy |
| 949 | + super_insert_overwrite = mocker.patch( |
| 950 | + "sqlmesh.core.engine_adapter.base.EngineAdapterWithIndexSupport._insert_overwrite_by_condition" |
| 951 | + ) |
| 952 | + |
| 953 | + adapter._insert_overwrite_by_condition( |
| 954 | + table_name="target", |
| 955 | + source_queries=source_queries, |
| 956 | + target_columns_to_types={ |
| 957 | + "id": exp.DataType.build("INT"), |
| 958 | + "value": exp.DataType.build("VARCHAR"), |
| 959 | + }, |
| 960 | + where=None, |
| 961 | + table_properties={"mssql_merge_exists": True}, |
| 962 | + ) |
| 963 | + |
| 964 | + # Should call super's MERGE strategy, not base DELETE+INSERT |
| 965 | + assert super_insert_overwrite.called |
| 966 | + assert not base_insert_overwrite.called |
| 967 | + |
| 968 | + |
| 969 | +def test_python_scd2_model_preserves_physical_properties(make_snapshot): |
| 970 | + @model( |
| 971 | + "test_schema.python_scd2_with_mssql_merge", |
| 972 | + kind=SCDType2ByTimeKind( |
| 973 | + unique_key=["id"], |
| 974 | + valid_from_name="valid_from", |
| 975 | + valid_to_name="valid_to", |
| 976 | + updated_at_name="updated_at", |
| 977 | + ), |
| 978 | + columns={ |
| 979 | + "id": "INT", |
| 980 | + "value": "VARCHAR", |
| 981 | + "updated_at": "TIMESTAMP", |
| 982 | + "valid_from": "TIMESTAMP", |
| 983 | + "valid_to": "TIMESTAMP", |
| 984 | + }, |
| 985 | + physical_properties={"mssql_merge_exists": True}, |
| 986 | + ) |
| 987 | + def python_scd2_model(context, **kwargs): |
| 988 | + import pandas as pd |
| 989 | + |
| 990 | + return pd.DataFrame( |
| 991 | + {"id": [1, 2], "value": ["a", "b"], "updated_at": ["2024-01-01", "2024-01-02"]} |
| 992 | + ) |
| 993 | + |
| 994 | + m = model.get_registry()["test_schema.python_scd2_with_mssql_merge"].model( |
| 995 | + module_path=Path("."), |
| 996 | + path=Path("."), |
| 997 | + dialect="tsql", |
| 998 | + ) |
| 999 | + |
| 1000 | + # verify model has physical_properties that trigger merge strategy |
| 1001 | + assert "mssql_merge_exists" in m.physical_properties |
| 1002 | + snapshot: Snapshot = make_snapshot(m) |
| 1003 | + assert snapshot.node.physical_properties == m.physical_properties |
| 1004 | + assert snapshot.node.physical_properties.get("mssql_merge_exists") |
0 commit comments