Skip to content

Commit 01e0c58

Browse files
authored
Support included columns in MicrosoftSQL indexes (#79)
1 parent 593b0de commit 01e0c58

File tree

9 files changed

+71
-5
lines changed

9 files changed

+71
-5
lines changed

.github/workflows/integration-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88
jobs:
99
microsoft_sql:
1010
if: github.event.pull_request.draft == false
11-
runs-on: ubuntu-latest
11+
runs-on: ubuntu-20.04
1212
env:
1313
RunDockerTests: true
1414
steps:

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<TargetFramework>net8.0</TargetFramework>
44
<Authors>TiCodeX</Authors>
5-
<Version>2024.12.1</Version>
5+
<Version>2025.3.1</Version>
66
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
77
<ImplicitUsings>enable</ImplicitUsings>
88
<IncludeServicesUsings>true</IncludeServicesUsings>

SQLSchemaCompare.Core/Entities/Database/ABaseDbIndex.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,19 @@ public class ABaseDbIndex : ABaseDbConstraint
1414
/// <remarks>Used only by the DatabaseProvider to group the indexes and fill the ColumnDescending list</remarks>
1515
public bool IsDescending { get; set; }
1616

17+
/// <summary>
18+
/// Gets or sets a value indicating whether the index is included.
19+
/// </summary>
20+
public bool IsIncluded { get; set; }
21+
1722
/// <summary>
1823
/// Gets whether the column is descending, sorted like the ColumnNames list
1924
/// </summary>
2025
public List<bool> ColumnDescending { get; } = new List<bool>();
26+
27+
/// <summary>
28+
/// Gets the included columns.
29+
/// </summary>
30+
public List<string> IncludedColumns { get; } = new List<string>();
2131
}
2232
}

SQLSchemaCompare.Infrastructure/DatabaseProviders/ADatabaseProvider.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,9 @@ protected TDatabase DiscoverDatabase(TDatabaseContext context, TaskInfo taskInfo
215215
{
216216
var index = indexGroup.First();
217217
index.Database = db;
218-
index.ColumnNames.AddRange(indexGroup.OrderBy(x => x.OrdinalPosition).Select(x => x.ColumnName));
219-
index.ColumnDescending.AddRange(indexGroup.OrderBy(x => x.OrdinalPosition).Select(x => x.IsDescending));
218+
index.ColumnNames.AddRange(indexGroup.Where(x => !x.IsIncluded).OrderBy(x => x.OrdinalPosition).Select(x => x.ColumnName));
219+
index.ColumnDescending.AddRange(indexGroup.Where(x => !x.IsIncluded).OrderBy(x => x.OrdinalPosition).Select(x => x.IsDescending));
220+
index.IncludedColumns.AddRange(indexGroup.Where(x => x.IsIncluded).OrderBy(x => x.OrdinalPosition).Select(x => x.ColumnName));
220221
db.Indexes.Add(index);
221222
}
222223
}

SQLSchemaCompare.Infrastructure/DatabaseProviders/MicrosoftSqlDatabaseProvider.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ protected override IEnumerable<ABaseDbIndex> GetIndexes(MicrosoftSqlDatabaseCont
207207
query.AppendLine(" CAST(ic.key_ordinal AS bigint) AS 'OrdinalPosition',");
208208
query.AppendLine(" i.type AS Type,");
209209
query.AppendLine(" i.is_unique AS 'IsUnique',");
210+
query.AppendLine(" ic.is_included_column AS 'IsIncluded',");
210211
query.AppendLine(" i.filter_definition AS 'FilterDefinition'");
211212
query.AppendLine("FROM sys.indexes i");
212213
query.AppendLine("JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id");

SQLSchemaCompare.Infrastructure/SqlScripters/MicrosoftSqlScripter.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,12 @@ protected override string ScriptCreateIndex(ABaseDbIndex index)
360360
}
361361

362362
sb.AppendLine($"INDEX {this.ScriptHelper.ScriptObjectName(index.Name)} ON {this.ScriptHelper.ScriptObjectName(index.TableSchema, index.TableName)}({string.Join(",", columnList)})");
363+
364+
if (index.IncludedColumns.Any())
365+
{
366+
sb.AppendLine($"INCLUDE({string.Join(",", index.IncludedColumns.Select(this.ScriptHelper.ScriptObjectName))})");
367+
}
368+
363369
if (!string.IsNullOrWhiteSpace(indexMicrosoft.FilterDefinition))
364370
{
365371
sb.AppendLine($"{Indent}WHERE {indexMicrosoft.FilterDefinition}");

SQLSchemaCompare.Test/Datasources/sakila-schema-microsoftsql.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ ALTER TABLE customer_data.address ADD CONSTRAINT [DF_address_last_update] DEFAUL
136136
GO
137137
CREATE INDEX idx_fk_city_id ON customer_data.address(city_id)
138138
GO
139+
CREATE INDEX idx_address_address2_district_postal_code ON customer_data.address(address, address2) INCLUDE(district, postal_code)
140+
GO
139141
ALTER TABLE customer_data.address WITH NOCHECK ADD CONSTRAINT fk_address_city FOREIGN KEY (city_id) REFERENCES customer_data.city (city_id) ON DELETE NO ACTION ON UPDATE CASCADE
140142
GO
141143
ALTER TABLE customer_data.address NOCHECK CONSTRAINT fk_address_city

SQLSchemaCompare.Test/Integration/MicrosoftSqlTests.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,21 @@ public void MigrateMicrosoftSqlDatabaseTargetMissingIndex(ushort port)
440440
this.dbFixture.AlterTargetDatabaseExecuteFullAndAllAlterScriptsAndCompare(DatabaseType.MicrosoftSql, sb.ToString(), port);
441441
}
442442

443+
/// <summary>
444+
/// Test migration script when target db doesn't have a index with included columns
445+
/// </summary>
446+
/// <param name="port">The port of the server</param>
447+
[Theory]
448+
[MemberData(nameof(DatabaseFixtureMicrosoftSql.ServerPorts), MemberType = typeof(DatabaseFixtureMicrosoftSql))]
449+
[IntegrationTest]
450+
[Category("MicrosoftSQL")]
451+
public void MigrateMicrosoftSqlDatabaseTargetMissingIndexWithIncludedColumns(ushort port)
452+
{
453+
var sb = new StringBuilder();
454+
sb.AppendLine("DROP INDEX idx_address_address2_district_postal_code ON customer_data.address");
455+
this.dbFixture.AlterTargetDatabaseExecuteFullAndAllAlterScriptsAndCompare(DatabaseType.MicrosoftSql, sb.ToString(), port);
456+
}
457+
443458
/// <summary>
444459
/// Test migration script when target db have an additional index
445460
/// </summary>
@@ -455,6 +470,21 @@ public void MigrateMicrosoftSqlDatabaseTargetExtraIndex(ushort port)
455470
this.dbFixture.AlterTargetDatabaseExecuteFullAndAllAlterScriptsAndCompare(DatabaseType.MicrosoftSql, sb.ToString(), port);
456471
}
457472

473+
/// <summary>
474+
/// Test migration script when target db have an additional index with included columns
475+
/// </summary>
476+
/// <param name="port">The port of the server</param>
477+
[Theory]
478+
[MemberData(nameof(DatabaseFixtureMicrosoftSql.ServerPorts), MemberType = typeof(DatabaseFixtureMicrosoftSql))]
479+
[IntegrationTest]
480+
[Category("MicrosoftSQL")]
481+
public void MigrateMicrosoftSqlDatabaseTargetExtraIndexWithIncludedColumns(ushort port)
482+
{
483+
var sb = new StringBuilder();
484+
sb.AppendLine("CREATE INDEX idx_title_release_year_special_features ON inventory.film (title, release_year) INCLUDE (special_features)");
485+
this.dbFixture.AlterTargetDatabaseExecuteFullAndAllAlterScriptsAndCompare(DatabaseType.MicrosoftSql, sb.ToString(), port);
486+
}
487+
458488
/// <summary>
459489
/// Test migration script when target db have a different filtered index
460490
/// </summary>
@@ -487,6 +517,22 @@ public void MigrateMicrosoftSqlDatabaseTargetDifferentIndexType(ushort port)
487517
this.dbFixture.AlterTargetDatabaseExecuteFullAndAllAlterScriptsAndCompare(DatabaseType.MicrosoftSql, sb.ToString(), port);
488518
}
489519

520+
/// <summary>
521+
/// Test migration script when target db have a different index included columns
522+
/// </summary>
523+
/// <param name="port">The port of the server</param>
524+
[Theory]
525+
[MemberData(nameof(DatabaseFixtureMicrosoftSql.ServerPorts), MemberType = typeof(DatabaseFixtureMicrosoftSql))]
526+
[IntegrationTest]
527+
[Category("MicrosoftSQL")]
528+
public void MigrateMicrosoftSqlDatabaseTargetDifferentIndexIncludedColumns(ushort port)
529+
{
530+
var sb = new StringBuilder();
531+
sb.AppendLine("DROP INDEX idx_address_address2_district_postal_code ON customer_data.address");
532+
sb.AppendLine("CREATE INDEX idx_address_address2_district_postal_code ON customer_data.address(address, address2) INCLUDE(district, postal_code, phone)");
533+
this.dbFixture.AlterTargetDatabaseExecuteFullAndAllAlterScriptsAndCompare(DatabaseType.MicrosoftSql, sb.ToString(), port);
534+
}
535+
490536
/// <summary>
491537
/// Test migration script when target db doesn't have a trigger
492538
/// </summary>

SQLSchemaCompare/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sqlschemacompare",
3-
"version": "2024.12.1",
3+
"version": "2025.3.1",
44
"license": "GPL-3.0-only",
55
"description": "The Swiss Army Knife of Database Schema Comparison for Microsoft SQL, mySQL and PostgreSQL which runs on Windows, Linux and macOS systems.",
66
"main": "app.js",

0 commit comments

Comments
 (0)