Skip to content

Commit f536263

Browse files
authored
Merge pull request #686 from joshmarkovic/master
perf: make hot-path adapter catalog queries sargable
2 parents 20e5905 + a31fb1f commit f536263

3 files changed

Lines changed: 105 additions & 65 deletions

File tree

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

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -69,31 +69,20 @@
6969
{% set query_label = apply_label() %}
7070
{% call statement('get_columns_in_relation', fetch_result=True) %}
7171
{{ get_use_database_sql(relation.database) }}
72-
with mapping as (
73-
select
74-
row_number() over (partition by object_name(c.object_id) order by c.column_id) as ordinal_position,
75-
c.name collate database_default as column_name,
76-
t.name as data_type,
77-
case
78-
when (t.name in ('nchar', 'nvarchar', 'sysname') and c.max_length <> -1) then c.max_length / 2
79-
else c.max_length
80-
end as character_maximum_length,
81-
c.precision as numeric_precision,
82-
c.scale as numeric_scale
83-
from sys.columns c {{ information_schema_hints() }}
84-
inner join sys.types t {{ information_schema_hints() }}
85-
on c.user_type_id = t.user_type_id
86-
where c.object_id = object_id('{{ 'tempdb..' ~ relation.include(database=false, schema=false) if '#' in relation.identifier else relation }}')
87-
)
88-
8972
select
90-
column_name,
91-
data_type,
92-
character_maximum_length,
93-
numeric_precision,
94-
numeric_scale
95-
from mapping
96-
order by ordinal_position
73+
c.name collate database_default as column_name,
74+
t.name as data_type,
75+
case
76+
when (t.name in ('nchar', 'nvarchar', 'sysname') and c.max_length <> -1) then c.max_length / 2
77+
else c.max_length
78+
end as character_maximum_length,
79+
c.precision as numeric_precision,
80+
c.scale as numeric_scale
81+
from sys.columns c {{ information_schema_hints() }}
82+
inner join sys.types t {{ information_schema_hints() }}
83+
on c.user_type_id = t.user_type_id
84+
where c.object_id = object_id('{{ 'tempdb..' ~ relation.include(database=false, schema=false) if '#' in relation.identifier else relation }}')
85+
order by c.column_id
9786
{{ query_label }}
9887
9988
{% endcall %}

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

Lines changed: 35 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -42,23 +42,22 @@
4242
{% macro sqlserver__list_relations_without_caching(schema_relation) -%}
4343
{% call statement('list_relations_without_caching', fetch_result=True) -%}
4444
{{ get_use_database_sql(schema_relation.database) }}
45-
with base as (
46-
select
47-
DB_NAME() as [database],
48-
t.name as [name],
49-
SCHEMA_NAME(t.schema_id) as [schema],
50-
'table' as table_type
51-
from sys.tables as t {{ information_schema_hints() }}
52-
union all
53-
select
54-
DB_NAME() as [database],
55-
v.name as [name],
56-
SCHEMA_NAME(v.schema_id) as [schema],
57-
'view' as table_type
58-
from sys.views as v {{ information_schema_hints() }}
59-
)
60-
select * from base
61-
where [schema] like '{{ schema_relation.schema }}'
45+
declare @schema_id int = schema_id('{{ schema_relation.schema }}');
46+
select
47+
DB_NAME() as [database],
48+
t.name as [name],
49+
'{{ schema_relation.schema }}' as [schema],
50+
'table' as table_type
51+
from sys.tables as t {{ information_schema_hints() }}
52+
where t.schema_id = @schema_id
53+
union all
54+
select
55+
DB_NAME() as [database],
56+
v.name as [name],
57+
'{{ schema_relation.schema }}' as [schema],
58+
'view' as table_type
59+
from sys.views as v {{ information_schema_hints() }}
60+
where v.schema_id = @schema_id
6261
{{ apply_label() }}
6362
{% endcall %}
6463
{{ return(load_result('list_relations_without_caching').table) }}
@@ -67,24 +66,22 @@
6766
{% macro sqlserver__get_relation_without_caching(schema_relation) -%}
6867
{% call statement('get_relation_without_caching', fetch_result=True) -%}
6968
{{ get_use_database_sql(schema_relation.database) }}
70-
with base as (
71-
select
72-
DB_NAME() as [database],
73-
t.name as [name],
74-
SCHEMA_NAME(t.schema_id) as [schema],
75-
'table' as table_type
76-
from sys.tables as t {{ information_schema_hints() }}
77-
union all
78-
select
79-
DB_NAME() as [database],
80-
v.name as [name],
81-
SCHEMA_NAME(v.schema_id) as [schema],
82-
'view' as table_type
83-
from sys.views as v {{ information_schema_hints() }}
84-
)
85-
select * from base
86-
where [schema] like '{{ schema_relation.schema }}'
87-
and [name] like '{{ schema_relation.identifier }}'
69+
declare @schema_id int = schema_id('{{ schema_relation.schema }}');
70+
select
71+
DB_NAME() as [database],
72+
t.name as [name],
73+
'{{ schema_relation.schema }}' as [schema],
74+
'table' as table_type
75+
from sys.tables as t {{ information_schema_hints() }}
76+
where t.schema_id = @schema_id and t.name = '{{ schema_relation.identifier }}'
77+
union all
78+
select
79+
DB_NAME() as [database],
80+
v.name as [name],
81+
'{{ schema_relation.schema }}' as [schema],
82+
'view' as table_type
83+
from sys.views as v {{ information_schema_hints() }}
84+
where v.schema_id = @schema_id and v.name = '{{ schema_relation.identifier }}'
8885
{{ apply_label() }}
8986
{% endcall %}
9087
{{ return(load_result('get_relation_without_caching').table) }}
@@ -95,13 +92,10 @@
9592
{% endmacro %}
9693

9794
{% macro sqlserver__get_view_definition_sql(relation) -%}
95+
{%- set object_name = "quotename('" ~ relation.schema ~ "') + '.' + quotename('" ~ relation.identifier ~ "')" -%}
9896
{{ get_use_database_sql(relation.database) }}
99-
select object_definition(v.object_id) as definition
100-
from sys.views as v {{ information_schema_hints() }}
101-
inner join sys.schemas as s {{ information_schema_hints() }}
102-
on v.schema_id = s.schema_id
103-
where upper(s.name) = upper('{{ relation.schema }}')
104-
and upper(v.name) = upper('{{ relation.identifier }}')
97+
select object_definition(object_id({{ object_name }}, 'V')) as definition
98+
where object_id({{ object_name }}, 'V') is not null
10599
{% endmacro %}
106100

107101
{% 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)