66base_validation = """
77with base_query AS (
88select i.[name] as index_name,
9- substring(column_names, 1, len(column_names)-1) as [columns],
10- case when i.[type] = 1 then 'Clustered index'
11- when i.[type] = 2 then 'Nonclustered unique index'
12- when i.[type] = 3 then 'XML index'
13- when i.[type] = 4 then 'Spatial index'
14- when i.[type] = 5 then 'Clustered columnstore index'
15- when i.[type] = 6 then 'Nonclustered columnstore index'
16- when i.[type] = 7 then 'Nonclustered hash index'
17- end as index_type,
18- case when i.is_unique = 1 then 'Unique'
19- else 'Not unique' end as [unique],
20- schema_name(t.schema_id) + '.' + t.[name] as table_view,
21- case when t.[type] = 'U' then 'Table'
22- when t.[type] = 'V' then 'View'
23- end as [object_type],
9+ substring(column_names, 1, len(column_names)-1) as [columns],
10+ substring(included_column_names, 1, len(included_column_names)-1) as included_columns,
11+ case when i.[type] = 1 then 'clustered'
12+ when i.[type] = 2 then 'nonclustered'
13+ when i.[type] = 3 then 'xml'
14+ when i.[type] = 4 then 'spatial'
15+ when i.[type] = 5 then 'clustered columnstore'
16+ when i.[type] = 6 then 'nonclustered columnstore'
17+ when i.[type] = 7 then 'nonclustered hash'
18+ end as index_type,
19+ case when i.is_unique = 1 then 'Unique'
20+ else 'Not unique' end as [unique],
21+ schema_name(t.schema_id) + '.' + t.[name] as table_view,
22+ case when t.[type] = 'U' then 'Table'
23+ when t.[type] = 'V' then 'View'
24+ end as [object_type],
2425 s.name as schema_name
2526from sys.objects t
2627 inner join sys.schemas s
27- on
28- t.schema_id = s.schema_id
29- inner join sys.indexes i
30- on t.object_id = i.object_id
31- cross apply (select col.[name] + ', '
32- from sys.index_columns ic
33- inner join sys.columns col
34- on ic.object_id = col.object_id
35- and ic.column_id = col.column_id
36- where ic.object_id = t.object_id
37- and ic.index_id = i.index_id
38- order by key_ordinal
39- for xml path ('') ) D (column_names)
28+ on
29+ t.schema_id = s.schema_id
30+ inner join sys.indexes i
31+ on t.object_id = i.object_id
32+ cross apply (select col.[name] + ', '
33+ from sys.index_columns ic
34+ inner join sys.columns col
35+ on ic.object_id = col.object_id
36+ and ic.column_id = col.column_id
37+ where ic.object_id = t.object_id
38+ and ic.index_id = i.index_id
39+ and ic.is_included_column = 0
40+ order by key_ordinal
41+ for xml path ('') ) D (column_names)
42+ cross apply (select col.[name] + ', '
43+ from sys.index_columns ic
44+ inner join sys.columns col
45+ on ic.object_id = col.object_id
46+ and ic.column_id = col.column_id
47+ where ic.object_id = t.object_id
48+ and ic.index_id = i.index_id
49+ and ic.is_included_column = 1
50+ order by key_ordinal
51+ for xml path ('') ) E (included_column_names)
4052where t.is_ms_shipped <> 1
4153and index_id > 0
4254)
4658 base_validation
4759 + """
4860select
49- index_type,
61+ index_type + case when [unique] = 'Unique' then ' unique' else '' end as index_type ,
5062 count(*) index_count
5163from
5264 base_query
5365WHERE
5466 schema_name='{schema_name}'
55- group by index_type
67+ group by index_type + case when [unique] = 'Unique' then ' unique' else '' end
5668"""
5769)
5870
6274SELECT
6375 index_name,
6476 [columns],
77+ [included_columns],
6578 index_type,
6679 [unique],
6780 table_view,
135148 {'columns': ['column_b']},
136149 {'columns': ['column_a', 'column_b']},
137150 {'columns': ['column_b', 'column_a'], 'type': 'clustered', 'unique': True},
138- {'columns': ['column_a'], 'type': 'nonclustered'}
151+ {'columns': ['column_a','column_c'],
152+ 'type': 'nonclustered',
153+ 'included_columns': ['column_b']},
154+ ]
155+ )
156+ }}
157+
158+ select 1 as column_a, 2 as column_b, 3 as column_c
159+
160+ """
161+
162+
163+ models__table_included_sql = """
164+ {{
165+ config(
166+ materialized = "table",
167+ as_columnstore = False,
168+ indexes=[
169+ {'columns': ['column_a'], 'included_columns': ['column_b']},
170+ {'columns': ['column_b'], 'type': 'clustered'}
139171 ]
140172 )
141173}}
@@ -248,6 +280,7 @@ def models(self):
248280 "table.sql" : models__table_sql ,
249281 "incremental.sql" : models__incremental_sql ,
250282 "columnstore.sql" : models__columnstore_sql ,
283+ "table_included.sql" : models__table_included_sql ,
251284 }
252285
253286 @pytest .fixture (scope = "class" )
@@ -265,8 +298,12 @@ def project_config_update(self):
265298 "seeds" : {
266299 "quote_columns" : False ,
267300 "indexes" : [
268- {"columns" : ["country_code" ], "unique" : False , "type" : "nonclustered" },
269- {"columns" : ["country_code" , "country_name" ], "unique" : True },
301+ {"columns" : ["country_code" ], "unique" : False },
302+ {
303+ "columns" : ["country_code" , "country_name" ],
304+ "unique" : True ,
305+ "type" : "clustered" ,
306+ },
270307 ],
271308 },
272309 "vars" : {
@@ -279,26 +316,85 @@ def test_table(self, project, unique_schema):
279316 assert len (results ) == 1
280317
281318 indexes = self .get_indexes ("table" , project , unique_schema )
319+ indexes = self .sort_indexes (indexes )
282320 expected = [
283- {"columns" : "column_a" , "unique" : False , "type" : "nonclustered" },
284- {"columns" : "column_b" , "unique" : False , "type" : "nonclustered" },
285- {"columns" : "column_a, column_b" , "unique" : False , "type" : "nonclustered" },
286- {"columns" : "column_b, column_a" , "unique" : True , "type" : "clustered" },
287- {"columns" : "column_a" , "unique" : False , "type" : "nonclustered" },
321+ {
322+ "columns" : "column_a" ,
323+ "unique" : False ,
324+ "type" : "nonclustered" ,
325+ "included_columns" : None ,
326+ },
327+ {
328+ "columns" : "column_a, column_b" ,
329+ "unique" : False ,
330+ "type" : "nonclustered" ,
331+ "included_columns" : None ,
332+ },
333+ {
334+ "columns" : "column_a, column_c" ,
335+ "unique" : False ,
336+ "type" : "nonclustered" ,
337+ "included_columns" : "column_b" ,
338+ },
339+ {
340+ "columns" : "column_b" ,
341+ "unique" : False ,
342+ "type" : "nonclustered" ,
343+ "included_columns" : None ,
344+ },
345+ {
346+ "columns" : "column_b, column_a" ,
347+ "unique" : True ,
348+ "type" : "clustered" ,
349+ "included_columns" : None ,
350+ },
351+ ]
352+ assert indexes == expected
353+
354+ def test_table_included (self , project , unique_schema ):
355+ results = run_dbt (["run" , "--models" , "table_included" ])
356+ assert len (results ) == 1
357+
358+ indexes = self .get_indexes ("table_included" , project , unique_schema )
359+ indexes = self .sort_indexes (indexes )
360+ expected = [
361+ {
362+ "columns" : "column_a" ,
363+ "unique" : False ,
364+ "type" : "nonclustered" ,
365+ "included_columns" : "column_b" ,
366+ },
367+ {
368+ "columns" : "column_b" ,
369+ "unique" : False ,
370+ "type" : "clustered" ,
371+ "included_columns" : None ,
372+ },
288373 ]
289- assert len ( indexes ) == len ( expected )
374+ assert indexes == expected
290375
291376 def test_incremental (self , project , unique_schema ):
292377 for additional_argument in [[], [], ["--full-refresh" ]]:
293378 results = run_dbt (["run" , "--models" , "incremental" ] + additional_argument )
294379 assert len (results ) == 1
295380
296381 indexes = self .get_indexes ("incremental" , project , unique_schema )
382+ indexes = self .sort_indexes (indexes )
297383 expected = [
298- {"columns" : "column_a" , "unique" : False , "type" : "nonclustered" },
299- {"columns" : "column_a, column_b" , "unique" : True , "type" : "nonclustered" },
384+ {
385+ "columns" : "column_a" ,
386+ "unique" : False ,
387+ "type" : "nonclustered" ,
388+ "included_columns" : None ,
389+ },
390+ {
391+ "columns" : "column_a, column_b" ,
392+ "unique" : True ,
393+ "type" : "nonclustered" ,
394+ "included_columns" : None ,
395+ },
300396 ]
301- assert len ( indexes ) == len ( expected )
397+ assert indexes == expected
302398
303399 def test_columnstore (self , project , unique_schema ):
304400 for additional_argument in [[], [], ["--full-refresh" ]]:
@@ -307,49 +403,79 @@ def test_columnstore(self, project, unique_schema):
307403
308404 indexes = self .get_indexes ("columnstore" , project , unique_schema )
309405 expected = [
310- {"columns" : "column_a" , "unique" : False , "type" : "columnstore" },
406+ {
407+ "columns" : "column_a" ,
408+ "unique" : False ,
409+ "type" : "columnstore" ,
410+ "included_columns" : None ,
411+ },
311412 ]
312- assert len (indexes ) == len (expected )
413+ assert len (indexes ) == len (
414+ expected
415+ ) # Nonclustered columnstore indexes meta is different
313416
314417 def test_seed (self , project , unique_schema ):
315418 for additional_argument in [[], [], ["--full-refresh" ]]:
316419 results = run_dbt (["seed" ] + additional_argument )
317420 assert len (results ) == 1
318421
319422 indexes = self .get_indexes ("seed" , project , unique_schema )
423+ indexes = self .sort_indexes (indexes )
320424 expected = [
321- {"columns" : "country_code" , "unique" : False , "type" : "nonclustered" },
322- {"columns" : "country_code, country_name" , "unique" : True , "type" : "clustered" },
425+ {
426+ "columns" : "country_code" ,
427+ "unique" : False ,
428+ "type" : "nonclustered" ,
429+ "included_columns" : None ,
430+ },
431+ {
432+ "columns" : "country_code, country_name" ,
433+ "unique" : True ,
434+ "type" : "clustered" ,
435+ "included_columns" : None ,
436+ },
323437 ]
324- assert len ( indexes ) == len ( expected )
438+ assert indexes == expected
325439
326440 def test_snapshot (self , project , unique_schema ):
327441 for version in [1 , 2 ]:
328442 results = run_dbt (["snapshot" , "--vars" , f"version: { version } " ])
329443 assert len (results ) == 1
330444
331445 indexes = self .get_indexes ("colors" , project , unique_schema )
446+ indexes = self .sort_indexes (indexes )
332447 expected = [
333- {"columns" : "id" , "unique" : False , "type" : "nonclustered" },
334- {"columns" : "id, color" , "unique" : True , "type" : "clustered" },
448+ {
449+ "columns" : "id" ,
450+ "unique" : False ,
451+ "type" : "nonclustered" ,
452+ "included_columns" : None ,
453+ },
454+ {
455+ "columns" : "id, color" ,
456+ "unique" : True ,
457+ "type" : "nonclustered" ,
458+ "included_columns" : None ,
459+ },
335460 ]
336- assert len ( indexes ) == len ( expected )
461+ assert indexes == expected
337462
338463 def get_indexes (self , table_name , project , unique_schema ):
339464 sql = indexes_def .format (schema_name = unique_schema , table_name = table_name )
340465 results = project .run_sql (sql , fetch = "all" )
341466 return [self .index_definition_dict (row ) for row in results ]
342467
343468 def index_definition_dict (self , index_definition ):
344- is_unique = index_definition [3 ] == "Unique"
469+ is_unique = index_definition [4 ] == "Unique"
345470 return {
346471 "columns" : index_definition [1 ],
472+ "included_columns" : index_definition [2 ],
347473 "unique" : is_unique ,
348- "type" : index_definition [2 ],
474+ "type" : index_definition [3 ],
349475 }
350476
351- def assertCountEqual (self , a , b ):
352- assert len ( a ) == len ( b )
477+ def sort_indexes (self , indexes ):
478+ return sorted ( indexes , key = lambda x : ( x [ "columns" ], x [ "type" ]) )
353479
354480
355481class TestSQLServerInvalidIndex :
0 commit comments