Skip to content

Commit 2c127f8

Browse files
authored
Merge pull request #520 from cody-scott/test-index-drops-schema
Update drop all schemas to target only schemas of a table in a given index
2 parents ddf278f + c3e3fe6 commit 2c127f8

File tree

4 files changed

+296
-0
lines changed

4 files changed

+296
-0
lines changed

dbt/include/sqlserver/macros/adapter/indexes.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
inner join sys.tables {{ information_schema_hints() }}
109109
on sys.indexes.object_id = sys.tables.object_id
110110
where sys.indexes.[name] is not null
111+
and SCHEMA_NAME(sys.tables.schema_id) = '{{ this.schema }}'
111112
and sys.tables.[name] = '{{ this.table }}'
112113
for xml path('')
113114
); exec sp_executesql @drop_remaining_indexes_last;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{% macro sqlserver__get_test_sql(main_sql, fail_calc, warn_if, error_if, limit) -%}
2+
3+
-- Create target schema if it does not
4+
USE [{{ target.database }}];
5+
IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = '{{ target.schema }}')
6+
BEGIN
7+
EXEC('CREATE SCHEMA [{{ target.schema }}]')
8+
END
9+
10+
{% set with_statement_pattern = 'with .+ as\s*\(' %}
11+
{% set re = modules.re %}
12+
{% set is_match = re.search(with_statement_pattern, main_sql, re.IGNORECASE) %}
13+
14+
{% if is_match %}
15+
{% set testview %}
16+
[{{ target.schema }}.testview_{{ range(1300, 19000) | random }}]
17+
{% endset %}
18+
19+
{% set sql = main_sql.replace("'", "''")%}
20+
EXEC('create view {{testview}} as {{ sql }};')
21+
select
22+
{{ "top (" ~ limit ~ ')' if limit != none }}
23+
{{ fail_calc }} as failures,
24+
case when {{ fail_calc }} {{ warn_if }}
25+
then 'true' else 'false' end as should_warn,
26+
case when {{ fail_calc }} {{ error_if }}
27+
then 'true' else 'false' end as should_error
28+
from (
29+
select * from {{testview}}
30+
) dbt_internal_test;
31+
32+
EXEC('drop view {{testview}};')
33+
34+
{% else -%}
35+
select
36+
{{ "top (" ~ limit ~ ')' if limit != none }}
37+
{{ fail_calc }} as failures,
38+
case when {{ fail_calc }} {{ warn_if }}
39+
then 'true' else 'false' end as should_warn,
40+
case when {{ fail_calc }} {{ error_if }}
41+
then 'true' else 'false' end as should_error
42+
from (
43+
{{ main_sql }}
44+
) dbt_internal_test
45+
{%- endif -%}
46+
{%- endmacro %}

tests/functional/adapter/mssql/test_index.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@
5454
select * from {{ ref('raw_data') }}
5555
"""
5656

57+
drop_schema_model = """
58+
{{
59+
config({
60+
"materialized": 'table',
61+
"post-hook": [
62+
"{{ drop_all_indexes_on_table() }}",
63+
]
64+
})
65+
}}
66+
select * from {{ ref('raw_data') }}
67+
"""
68+
5769
base_validation = """
5870
with base_query AS (
5971
select i.[name] as index_name,
@@ -107,6 +119,21 @@
107119
"""
108120
)
109121

122+
other_index_count = (
123+
base_validation
124+
+ """
125+
SELECT
126+
*
127+
FROM
128+
base_query
129+
WHERE
130+
schema_name='{schema_name}'
131+
AND
132+
table_view='{schema_name}.{table_name}'
133+
134+
"""
135+
)
136+
110137

111138
class TestIndex:
112139
@pytest.fixture(scope="class")
@@ -143,3 +170,77 @@ def test_create_index(self, project):
143170
"Nonclustered unique index": 4,
144171
}
145172
assert schema_dict == expected
173+
174+
175+
class TestIndexDropsOnlySchema:
176+
@pytest.fixture(scope="class")
177+
def project_config_update(self):
178+
return {"name": "generic_tests"}
179+
180+
@pytest.fixture(scope="class")
181+
def seeds(self):
182+
return {
183+
"raw_data.csv": index_seed_csv,
184+
"schema.yml": index_schema_base_yml,
185+
}
186+
187+
@pytest.fixture(scope="class")
188+
def models(self):
189+
return {
190+
"index_model.sql": drop_schema_model,
191+
"index_ccs_model.sql": model_sql_ccs,
192+
"schema.yml": model_yml,
193+
}
194+
195+
def create_table_and_index_other_schema(self, project):
196+
_schema = project.test_schema + "other"
197+
create_sql = f"""
198+
USE [{project.database}];
199+
IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = '{_schema}')
200+
BEGIN
201+
EXEC('CREATE SCHEMA [{_schema}]')
202+
END
203+
"""
204+
205+
create_table = f"""
206+
CREATE TABLE {_schema}.index_model (
207+
IDCOL BIGINT
208+
)
209+
"""
210+
211+
create_index = f"""
212+
CREATE INDEX sample_schema ON {_schema}.index_model (IDCOL)
213+
"""
214+
with get_connection(project.adapter):
215+
project.adapter.execute(create_sql, fetch=True)
216+
project.adapter.execute(create_table)
217+
project.adapter.execute(create_index)
218+
219+
def drop_schema_artifacts(self, project):
220+
_schema = project.test_schema + "other"
221+
drop_index = f"DROP INDEX IF EXISTS sample_schema ON {_schema}.index_model"
222+
drop_table = f"DROP TABLE IF EXISTS {_schema}.index_model"
223+
drop_schema = f"DROP SCHEMA IF EXISTS {_schema}"
224+
225+
with get_connection(project.adapter):
226+
project.adapter.execute(drop_index, fetch=True)
227+
project.adapter.execute(drop_table)
228+
project.adapter.execute(drop_schema)
229+
230+
def validate_other_schema(self, project):
231+
with get_connection(project.adapter):
232+
result, table = project.adapter.execute(
233+
other_index_count.format(
234+
schema_name=project.test_schema + "other", table_name="index_model"
235+
),
236+
fetch=True,
237+
)
238+
239+
assert len(table.rows) == 1
240+
241+
def test_create_index(self, project):
242+
self.create_table_and_index_other_schema(project)
243+
run_dbt(["seed"])
244+
run_dbt(["run"])
245+
self.validate_other_schema(project)
246+
self.drop_schema_artifacts(project)
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import pytest
2+
from dbt.tests.util import run_dbt
3+
4+
sample_model = """
5+
SELECT
6+
1 as ID,
7+
'a' as data
8+
9+
UNION ALL
10+
11+
SELECT
12+
2 as ID,
13+
'b' as data
14+
15+
UNION ALL
16+
17+
SELECT
18+
2 as ID,
19+
'c' as data
20+
"""
21+
22+
pass_model_yml = """
23+
version: 2
24+
models:
25+
- name: sample_model
26+
data_tests:
27+
- with_statement_pass:
28+
field: ID
29+
"""
30+
31+
fail_model_yml = """
32+
version: 2
33+
models:
34+
- name: sample_model
35+
data_tests:
36+
- with_statement_fail:
37+
field: ID
38+
"""
39+
40+
comments_model_yml = """
41+
version: 2
42+
models:
43+
- name: sample_model
44+
data_tests:
45+
- with_statement_comments:
46+
field: ID
47+
"""
48+
49+
with_test_fail_sql = """
50+
{% test with_statement_fail(model, field) %}
51+
52+
with test_sample AS (
53+
SELECT {{ field }} FROM {{ model }}
54+
GROUP BY {{ field }}
55+
HAVING COUNT(*) > 1
56+
)
57+
SELECT * FROM test_sample
58+
59+
{% endtest %}
60+
"""
61+
62+
with_test_pass_sql = """
63+
{% test with_statement_pass(model, field) %}
64+
65+
with test_sample AS (
66+
SELECT {{ field }} FROM {{ model }}
67+
GROUP BY {{ field }}
68+
HAVING COUNT(*) > 2
69+
)
70+
SELECT * FROM test_sample
71+
72+
{% endtest %}
73+
"""
74+
75+
with_test_with_comments_sql = """
76+
{% test with_statement_comments(model, field) %}
77+
-- comments
78+
with test_sample AS (
79+
SELECT {{ field }} FROM {{ model }}
80+
GROUP BY {{ field }}
81+
HAVING COUNT(*) > 2
82+
)
83+
SELECT * FROM test_sample
84+
{% endtest %}
85+
"""
86+
87+
88+
class BaseSQLTestWith:
89+
@pytest.fixture(scope="class")
90+
def project_config_update(self):
91+
return {
92+
"config-version": 2,
93+
"macro-paths": ["macros"],
94+
}
95+
96+
@pytest.fixture(scope="class")
97+
def macros(self):
98+
return {
99+
"with_statement_pass.sql": with_test_pass_sql,
100+
"with_statement_fail.sql": with_test_fail_sql,
101+
"with_statement_comments.sql": with_test_with_comments_sql,
102+
}
103+
104+
@pytest.fixture(scope="class")
105+
def models(self):
106+
return {
107+
"sample_model.sql": sample_model,
108+
"schema.yml": pass_model_yml,
109+
}
110+
111+
112+
class TestSQLTestWithPass(BaseSQLTestWith):
113+
@pytest.fixture(scope="class")
114+
def models(self):
115+
return {
116+
"sample_model.sql": sample_model,
117+
"schema.yml": pass_model_yml,
118+
}
119+
120+
def test_sql_test_contains_with(self, project):
121+
run_dbt(["run"])
122+
run_dbt(["test"])
123+
124+
125+
class TestSQLTestWithFail(BaseSQLTestWith):
126+
@pytest.fixture(scope="class")
127+
def models(self):
128+
return {
129+
"sample_model.sql": sample_model,
130+
"schema.yml": fail_model_yml,
131+
}
132+
133+
def test_sql_test_contains_with(self, project):
134+
run_dbt(["run"])
135+
run_dbt(["test"], expect_pass=False)
136+
137+
138+
class TestSQLTestWithComment(BaseSQLTestWith):
139+
@pytest.fixture(scope="class")
140+
def models(self):
141+
return {
142+
"sample_model.sql": sample_model,
143+
"schema.yml": comments_model_yml,
144+
}
145+
146+
def test_sql_test_contains_with(self, project):
147+
run_dbt(["run"])
148+
run_dbt(["test"])

0 commit comments

Comments
 (0)