Skip to content

Commit a31fb1f

Browse files
joshmarkovicclaude
andcommitted
fix: escape ] in get_view_definition_sql via quotename
Address PR review feedback on get_view_definition_sql: - Build the object name with quotename(schema) + '.' + quotename(identifier) instead of manual [..].[..] bracketing. quotename() doubles embedded ] characters, so legal identifiers containing ] no longer produce a malformed object name (which OBJECT_ID would resolve to NULL). - Add a focused functional test (runs under both CI collations, CS_AS and CI_AS) covering an identifier that contains ] and the zero-row behavior for a missing view that the view materialization's diff-skip relies on. OBJECT_ID resolution still follows database collation rules. In the view materialization, get_view_definition_sql is only ever called on the cached existing_relation, whose casing comes straight from the catalog, so the lookup casing always matches the stored object even under CS_AS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ba27fef commit a31fb1f

2 files changed

Lines changed: 60 additions & 2 deletions

File tree

dbt/include/sqlserver/macros/adapters/metadata.sql

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,10 @@
9292
{% endmacro %}
9393

9494
{% macro sqlserver__get_view_definition_sql(relation) -%}
95+
{%- set object_name = "quotename('" ~ relation.schema ~ "') + '.' + quotename('" ~ relation.identifier ~ "')" -%}
9596
{{ get_use_database_sql(relation.database) }}
96-
select object_definition(object_id('[{{ relation.schema }}].[{{ relation.identifier }}]', 'V')) as definition
97-
where object_id('[{{ relation.schema }}].[{{ relation.identifier }}]', 'V') is not null
97+
select object_definition(object_id({{ object_name }}, 'V')) as definition
98+
where object_id({{ object_name }}, 'V') is not null
9899
{% endmacro %}
99100

100101
{% macro sqlserver__get_relation_last_modified(information_schema, relations) -%}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import pytest
2+
3+
from dbt.tests.util import run_dbt_and_capture
4+
5+
# Builds a relation for the given schema/identifier and runs the adapter's
6+
# get_view_definition_sql against it, logging whether a definition row came
7+
# back. Lets the tests assert the macro's behaviour directly instead of going
8+
# through the full view materialization.
9+
VALIDATE_GET_VIEW_DEFINITION_MACRO = """
10+
{% macro validate_get_view_definition_sql(schema, identifier) -%}
11+
{% set relation = api.Relation.create(
12+
database=target.database, schema=schema, identifier=identifier, type='view'
13+
) %}
14+
{% set result = run_query(get_view_definition_sql(relation)) %}
15+
{% if result is not none and result.rows | length > 0 and result.rows[0][0] is not none %}
16+
{{ log("view_definition_found: true") }}
17+
{% else %}
18+
{{ log("view_definition_found: false") }}
19+
{% endif %}
20+
{% endmacro %}
21+
"""
22+
23+
24+
class TestGetViewDefinitionSql:
25+
@pytest.fixture(scope="class")
26+
def macros(self):
27+
return {"validate_get_view_definition_sql.sql": VALIDATE_GET_VIEW_DEFINITION_MACRO}
28+
29+
def _resolve(self, schema, identifier):
30+
kwargs = {"schema": schema, "identifier": identifier}
31+
_, log_output = run_dbt_and_capture(
32+
[
33+
"--debug",
34+
"run-operation",
35+
"validate_get_view_definition_sql",
36+
"--args",
37+
str(kwargs),
38+
]
39+
)
40+
return log_output
41+
42+
def test_resolves_identifier_containing_right_bracket(self, project):
43+
"""A legal identifier containing ``]`` must still resolve. quotename()
44+
doubles the ``]`` so OBJECT_ID gets a valid object name; the old manual
45+
bracket-quoting produced a malformed name and returned NULL."""
46+
identifier = "weird]name"
47+
# ``]`` is escaped by doubling it inside a bracketed identifier.
48+
project.run_sql(f"create view {project.test_schema}.[weird]]name] as select 1 as id")
49+
50+
log_output = self._resolve(project.test_schema, identifier)
51+
assert "view_definition_found: true" in log_output
52+
53+
def test_missing_view_returns_no_rows(self, project):
54+
"""A view that does not exist yields zero rows, which the view
55+
materialization relies on to fall through to a create."""
56+
log_output = self._resolve(project.test_schema, "does_not_exist")
57+
assert "view_definition_found: false" in log_output

0 commit comments

Comments
 (0)