6
6
base_validation = """
7
7
with base_query AS (
8
8
select 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],
24
25
s.name as schema_name
25
26
from sys.objects t
26
27
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)
40
52
where t.is_ms_shipped <> 1
41
53
and index_id > 0
42
54
)
46
58
base_validation
47
59
+ """
48
60
select
49
- index_type,
61
+ index_type + case when [unique] = 'Unique' then ' unique' else '' end as index_type ,
50
62
count(*) index_count
51
63
from
52
64
base_query
53
65
WHERE
54
66
schema_name='{schema_name}'
55
- group by index_type
67
+ group by index_type + case when [unique] = 'Unique' then ' unique' else '' end
56
68
"""
57
69
)
58
70
62
74
SELECT
63
75
index_name,
64
76
[columns],
77
+ [included_columns],
65
78
index_type,
66
79
[unique],
67
80
table_view,
135
148
{'columns': ['column_b']},
136
149
{'columns': ['column_a', 'column_b']},
137
150
{'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'}
139
171
]
140
172
)
141
173
}}
@@ -248,6 +280,7 @@ def models(self):
248
280
"table.sql" : models__table_sql ,
249
281
"incremental.sql" : models__incremental_sql ,
250
282
"columnstore.sql" : models__columnstore_sql ,
283
+ "table_included.sql" : models__table_included_sql ,
251
284
}
252
285
253
286
@pytest .fixture (scope = "class" )
@@ -265,8 +298,12 @@ def project_config_update(self):
265
298
"seeds" : {
266
299
"quote_columns" : False ,
267
300
"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
+ },
270
307
],
271
308
},
272
309
"vars" : {
@@ -279,26 +316,85 @@ def test_table(self, project, unique_schema):
279
316
assert len (results ) == 1
280
317
281
318
indexes = self .get_indexes ("table" , project , unique_schema )
319
+ indexes = self .sort_indexes (indexes )
282
320
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
+ },
288
373
]
289
- assert len ( indexes ) == len ( expected )
374
+ assert indexes == expected
290
375
291
376
def test_incremental (self , project , unique_schema ):
292
377
for additional_argument in [[], [], ["--full-refresh" ]]:
293
378
results = run_dbt (["run" , "--models" , "incremental" ] + additional_argument )
294
379
assert len (results ) == 1
295
380
296
381
indexes = self .get_indexes ("incremental" , project , unique_schema )
382
+ indexes = self .sort_indexes (indexes )
297
383
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
+ },
300
396
]
301
- assert len ( indexes ) == len ( expected )
397
+ assert indexes == expected
302
398
303
399
def test_columnstore (self , project , unique_schema ):
304
400
for additional_argument in [[], [], ["--full-refresh" ]]:
@@ -307,49 +403,79 @@ def test_columnstore(self, project, unique_schema):
307
403
308
404
indexes = self .get_indexes ("columnstore" , project , unique_schema )
309
405
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
+ },
311
412
]
312
- assert len (indexes ) == len (expected )
413
+ assert len (indexes ) == len (
414
+ expected
415
+ ) # Nonclustered columnstore indexes meta is different
313
416
314
417
def test_seed (self , project , unique_schema ):
315
418
for additional_argument in [[], [], ["--full-refresh" ]]:
316
419
results = run_dbt (["seed" ] + additional_argument )
317
420
assert len (results ) == 1
318
421
319
422
indexes = self .get_indexes ("seed" , project , unique_schema )
423
+ indexes = self .sort_indexes (indexes )
320
424
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
+ },
323
437
]
324
- assert len ( indexes ) == len ( expected )
438
+ assert indexes == expected
325
439
326
440
def test_snapshot (self , project , unique_schema ):
327
441
for version in [1 , 2 ]:
328
442
results = run_dbt (["snapshot" , "--vars" , f"version: { version } " ])
329
443
assert len (results ) == 1
330
444
331
445
indexes = self .get_indexes ("colors" , project , unique_schema )
446
+ indexes = self .sort_indexes (indexes )
332
447
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
+ },
335
460
]
336
- assert len ( indexes ) == len ( expected )
461
+ assert indexes == expected
337
462
338
463
def get_indexes (self , table_name , project , unique_schema ):
339
464
sql = indexes_def .format (schema_name = unique_schema , table_name = table_name )
340
465
results = project .run_sql (sql , fetch = "all" )
341
466
return [self .index_definition_dict (row ) for row in results ]
342
467
343
468
def index_definition_dict (self , index_definition ):
344
- is_unique = index_definition [3 ] == "Unique"
469
+ is_unique = index_definition [4 ] == "Unique"
345
470
return {
346
471
"columns" : index_definition [1 ],
472
+ "included_columns" : index_definition [2 ],
347
473
"unique" : is_unique ,
348
- "type" : index_definition [2 ],
474
+ "type" : index_definition [3 ],
349
475
}
350
476
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" ]) )
353
479
354
480
355
481
class TestSQLServerInvalidIndex :
0 commit comments