Skip to content

Commit 11d07c3

Browse files
committed
add static validation + documentation + more tests
Signed-off-by: Christian Troelsen <christian.troelsen@tryg.dk>
1 parent b89b05c commit 11d07c3

5 files changed

Lines changed: 175 additions & 2 deletions

File tree

docs/integrations/engines/databricks.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,28 @@ The only relevant SQLMesh configuration parameter is the optional `catalog` para
271271
| `disable_databricks_connect` | When running locally, disable the use of Databricks Connect for all model operations (so use SQL Connector for all models) | bool | N |
272272
| `disable_spark_session` | Do not use SparkSession if it is available (like when running in a notebook). | bool | N |
273273

274+
### Query tags
275+
276+
Databricks SQL Connector supports per-query tags through the `query_tags` model session property. Specify tags as a `MAP(...)` of string keys to string or `NULL` values:
277+
278+
```sql
279+
MODEL (
280+
name sqlmesh_example.tagged_model,
281+
dialect databricks,
282+
session_properties (
283+
query_tags = MAP(
284+
'team', 'data-eng',
285+
'app', 'sqlmesh',
286+
'feature', NULL
287+
)
288+
)
289+
);
290+
291+
SELECT 1 AS id;
292+
```
293+
294+
Query tags are only applied when SQLMesh executes SQL through the Databricks SQL Connector. They are not applied when SQLMesh routes execution through Databricks Connect, a Databricks notebook SparkSession, or the Spark engine adapter.
295+
274296
## Model table properties to support altering tables
275297

276298
If you are making a change to the structure of a table that is [forward only](../../guides/incremental_time.md#forward-only-models), then you may need to add the following to your model's `physical_properties`:

sqlmesh/core/engine_adapter/databricks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def _query_tags(
3636
if not query_tags:
3737
return None
3838

39-
if not isinstance(query_tags, exp.Map):
39+
if not isinstance(query_tags, (exp.Map, exp.VarMap)):
4040
raise SQLMeshError("Invalid value for `session_properties.query_tags`. Must be a map.")
4141

4242
keys = query_tags.args.get("keys")

sqlmesh/core/model/meta.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,38 @@ def session_properties_validator(cls, v: t.Any, info: ValidationInfo) -> t.Any:
396396
raise ConfigError(
397397
"Invalid value for `session_properties.authorization`. Must be a string literal."
398398
)
399+
elif prop_name == "query_tags":
400+
query_tags = eq.right
401+
if isinstance(query_tags, (d.MacroFunc, d.MacroVar)):
402+
continue
403+
404+
if not isinstance(query_tags, (exp.Map, exp.VarMap)):
405+
raise ConfigError(
406+
"Invalid value for `session_properties.query_tags`. Must be a map."
407+
)
408+
409+
keys = query_tags.args.get("keys")
410+
values = query_tags.args.get("values")
411+
if not isinstance(keys, exp.Array) or not isinstance(values, exp.Array):
412+
raise ConfigError(
413+
"Invalid value for `session_properties.query_tags`. Must be a map with array "
414+
"keys and array values."
415+
)
416+
417+
for key, value in zip(keys.expressions, values.expressions):
418+
if not isinstance(key, exp.Literal) or not key.is_string:
419+
raise ConfigError(
420+
"Invalid key in `session_properties.query_tags`. Keys must be string literals."
421+
)
422+
423+
if not (
424+
isinstance(value, exp.Null)
425+
or (isinstance(value, exp.Literal) and value.is_string)
426+
):
427+
raise ConfigError(
428+
"Invalid value in `session_properties.query_tags`. Values must be string "
429+
"literals or NULL."
430+
)
399431

400432
return parsed_session_properties
401433

tests/core/engine_adapter/test_databricks.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,13 @@ def test_session_query_tags(mocker: MockFixture, make_mocked_engine_adapter: t.C
135135
)
136136
adapter = make_mocked_engine_adapter(DatabricksEngineAdapter, default_catalog="test_catalog")
137137

138-
with adapter.session({"query_tags": _query_tags_map("team", "data-eng", "app", "sqlmesh")}):
138+
with adapter.session(
139+
{
140+
"query_tags": d.parse_one(
141+
"MAP('team', 'data-eng', 'app', 'sqlmesh')", dialect="databricks"
142+
)
143+
}
144+
):
139145
adapter.execute("SELECT 1")
140146

141147
adapter.cursor.execute.assert_called_with(

tests/core/test_model.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5190,6 +5190,90 @@ def test_session_properties_authorization_validation():
51905190
)
51915191

51925192

5193+
def test_session_properties_query_tags_validation():
5194+
model = load_sql_based_model(
5195+
d.parse(
5196+
"""
5197+
MODEL (
5198+
name test_schema.test_model,
5199+
dialect databricks,
5200+
session_properties (
5201+
query_tags = MAP('team', 'data-eng', 'app', 'sqlmesh', 'feature', NULL)
5202+
)
5203+
);
5204+
SELECT a FROM tbl;
5205+
""",
5206+
default_dialect="databricks",
5207+
)
5208+
)
5209+
assert model.session_properties == {
5210+
"query_tags": parse_one(
5211+
"MAP('team', 'data-eng', 'app', 'sqlmesh', 'feature', NULL)",
5212+
dialect="databricks",
5213+
)
5214+
}
5215+
5216+
with pytest.raises(
5217+
ConfigError,
5218+
match=r"Invalid value for `session_properties.query_tags`. Must be a map.",
5219+
):
5220+
load_sql_based_model(
5221+
d.parse(
5222+
"""
5223+
MODEL (
5224+
name test_schema.test_model,
5225+
dialect databricks,
5226+
session_properties (
5227+
query_tags = 'invalid value'
5228+
)
5229+
);
5230+
SELECT a FROM tbl;
5231+
""",
5232+
default_dialect="databricks",
5233+
)
5234+
)
5235+
5236+
with pytest.raises(
5237+
ConfigError,
5238+
match=r"Invalid key in `session_properties.query_tags`. Keys must be string literals.",
5239+
):
5240+
load_sql_based_model(
5241+
d.parse(
5242+
"""
5243+
MODEL (
5244+
name test_schema.test_model,
5245+
dialect databricks,
5246+
session_properties (
5247+
query_tags = MAP(1, 'data-eng')
5248+
)
5249+
);
5250+
SELECT a FROM tbl;
5251+
""",
5252+
default_dialect="databricks",
5253+
)
5254+
)
5255+
5256+
with pytest.raises(
5257+
ConfigError,
5258+
match=r"Invalid value in `session_properties.query_tags`. Values must be string literals or NULL.",
5259+
):
5260+
load_sql_based_model(
5261+
d.parse(
5262+
"""
5263+
MODEL (
5264+
name test_schema.test_model,
5265+
dialect databricks,
5266+
session_properties (
5267+
query_tags = MAP('team', 1)
5268+
)
5269+
);
5270+
SELECT a FROM tbl;
5271+
""",
5272+
default_dialect="databricks",
5273+
)
5274+
)
5275+
5276+
51935277
def test_model_jinja_macro_rendering():
51945278
expressions = d.parse(
51955279
"""
@@ -11719,6 +11803,35 @@ def test_authorization_macro(evaluator):
1171911803
}
1172011804

1172111805

11806+
def test_query_tags_macro() -> None:
11807+
@macro()
11808+
def test_query_tags_macro(evaluator):
11809+
return "MAP('team', 'data-eng')"
11810+
11811+
expressions = d.parse(
11812+
"""
11813+
MODEL (
11814+
name db.table,
11815+
dialect databricks,
11816+
session_properties (
11817+
query_tags = @test_query_tags_macro()
11818+
)
11819+
);
11820+
11821+
SELECT 1 AS c;
11822+
"""
11823+
)
11824+
11825+
model = load_sql_based_model(expressions)
11826+
assert model.session_properties == {
11827+
"query_tags": d.parse_one("@test_query_tags_macro()"),
11828+
}
11829+
11830+
assert model.render_session_properties() == {
11831+
"query_tags": d.parse_one("MAP('team', 'data-eng')", dialect="databricks"),
11832+
}
11833+
11834+
1172211835
def test_boolean_property_validation() -> None:
1172311836
expressions = d.parse(
1172411837
"""

0 commit comments

Comments
 (0)