Skip to content

Commit 76120fd

Browse files
JaBistDuNarrischJaBistDuNarrisch
authored andcommitted
Added sql assertion in tests for AddIndex
1 parent c555953 commit 76120fd

12 files changed

+233
-28
lines changed

src/Migrator.Tests/Providers/Generic/Generic_AddIndexTestsBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public void AddIndex_UsingNonIndexInstanceOverload_NonUnique_ShouldBeReadable()
5252
const string columnName = "TestColumn";
5353
const string indexName = "TestIndexName";
5454

55-
Provider.AddTable(tableName, new Column(columnName, System.Data.DbType.Int32));
55+
Provider.AddTable(tableName, new Column(columnName, DbType.Int32));
5656

5757
// Act
5858
Provider.AddIndex(indexName, tableName, columnName);

src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_AddIndexTests.cs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1+
using System;
2+
using System.Collections.Generic;
13
using System.Data;
4+
using System.Globalization;
25
using System.Linq;
36
using System.Threading.Tasks;
47
using DotNetProjects.Migrator.Framework;
8+
using DotNetProjects.Migrator.Providers.Models.Indexes;
9+
using DotNetProjects.Migrator.Providers.Models.Indexes.Enums;
510
using Migrator.Tests.Providers.Generic;
611
using NUnit.Framework;
712
using Oracle.ManagedDataAccess.Client;
13+
using Index = DotNetProjects.Migrator.Framework.Index;
814

915
namespace Migrator.Tests.Providers.OracleProvider;
1016

@@ -46,4 +52,130 @@ public void AddIndex_Unique_Success()
4652
Assert.That(index.Unique, Is.True);
4753
Assert.That(ex.Number, Is.EqualTo(1));
4854
}
55+
56+
/// <summary>
57+
/// This test is located in the dedicated database type folder not in the base class since <see cref="OracleTransformationProvider.GetIndexes"/>
58+
/// cannot read filter items.
59+
/// </summary>
60+
[Test]
61+
public void AddIndex_FilteredIndexMiscellaneousFilterTypesAndDataTypes_Success()
62+
{
63+
// Arrange
64+
const string tableName = "TestTable";
65+
const string columnName1 = "TestColumn1";
66+
const string columnName2 = "TestColumn2";
67+
const string columnName3 = "TestColumn3";
68+
const string columnName4 = "TestColumn4";
69+
const string columnName5 = "TestColumn5";
70+
const string columnName6 = "TestColumn6";
71+
const string columnName7 = "TestColumn7";
72+
const string columnName8 = "TestColumn8";
73+
const string columnName9 = "TestColumn9";
74+
const string columnName10 = "TestColumn10";
75+
const string columnName11 = "TestColumn11";
76+
const string columnName12 = "TestColumn12";
77+
const string columnName13 = "TestColumn13";
78+
79+
const string indexName = "TestIndexName";
80+
81+
Provider.AddTable(tableName,
82+
new Column(columnName1, DbType.Int16),
83+
new Column(columnName2, DbType.Int32),
84+
new Column(columnName3, DbType.Int64),
85+
new Column(columnName4, DbType.UInt16),
86+
new Column(columnName5, DbType.UInt32),
87+
new Column(columnName6, DbType.UInt64),
88+
new Column(columnName7, DbType.String),
89+
new Column(columnName8, DbType.Int32),
90+
new Column(columnName9, DbType.Int32),
91+
new Column(columnName10, DbType.Int32),
92+
new Column(columnName11, DbType.Int32),
93+
new Column(columnName12, DbType.Int32),
94+
new Column(columnName13, DbType.Int32)
95+
);
96+
97+
List<FilterItem> filterItems = [
98+
new() { Filter = FilterType.EqualTo, ColumnName = columnName1, Value = 1 },
99+
new() { Filter = FilterType.GreaterThan, ColumnName = columnName2, Value = 2 },
100+
new() { Filter = FilterType.GreaterThanOrEqualTo, ColumnName = columnName3, Value = 2323 },
101+
new() { Filter = FilterType.NotEqualTo, ColumnName = columnName4, Value = 3434 },
102+
new() { Filter = FilterType.NotEqualTo, ColumnName = columnName5, Value = -3434 },
103+
new() { Filter = FilterType.SmallerThan, ColumnName = columnName6, Value = 3434345345 },
104+
new() { Filter = FilterType.NotEqualTo, ColumnName = columnName7, Value = "asdf" },
105+
new() { Filter = FilterType.EqualTo, ColumnName = columnName8, Value = 11 },
106+
new() { Filter = FilterType.GreaterThan, ColumnName = columnName9, Value = 22 },
107+
new() { Filter = FilterType.GreaterThanOrEqualTo, ColumnName = columnName10, Value = 33 },
108+
new() { Filter = FilterType.NotEqualTo, ColumnName = columnName11, Value = 44 },
109+
new() { Filter = FilterType.SmallerThan, ColumnName = columnName12, Value = 55 },
110+
new() { Filter = FilterType.SmallerThanOrEqualTo, ColumnName = columnName13, Value = 66 }
111+
];
112+
113+
// Act
114+
var addIndexSql = Provider.AddIndex(tableName,
115+
new Index
116+
{
117+
Name = indexName,
118+
KeyColumns = [
119+
columnName1,
120+
columnName2,
121+
columnName3,
122+
columnName4,
123+
columnName5,
124+
columnName6,
125+
columnName7,
126+
columnName8,
127+
columnName9,
128+
columnName10,
129+
columnName11,
130+
columnName12,
131+
columnName13
132+
],
133+
Unique = false,
134+
FilterItems = filterItems
135+
});
136+
137+
// Assert
138+
var indexesFromDatabase = Provider.GetIndexes(table: tableName);
139+
140+
// In Oracle it seems that functional expressions are stored as index. FilterItems are not implemented in GetIndexes for Oracle. No further
141+
// assert possible at this point in time.
142+
Assert.That(indexesFromDatabase.Single().KeyColumns.Count, Is.EqualTo(13));
143+
144+
145+
var expectedSql = "CREATE INDEX TestIndexName ON TestTable (CASE WHEN TestColumn1 = 1 THEN TestColumn1 ELSE NULL END, CASE WHEN TestColumn2 > 2 THEN TestColumn2 ELSE NULL END, CASE WHEN TestColumn3 >= 2323 THEN TestColumn3 ELSE NULL END, CASE WHEN TestColumn4 <> 3434 THEN TestColumn4 ELSE NULL END, CASE WHEN TestColumn5 <> -3434 THEN TestColumn5 ELSE NULL END, CASE WHEN TestColumn6 < 3434345345 THEN TestColumn6 ELSE NULL END, CASE WHEN TestColumn7 <> 'asdf' THEN TestColumn7 ELSE NULL END, CASE WHEN TestColumn8 = 11 THEN TestColumn8 ELSE NULL END, CASE WHEN TestColumn9 > 22 THEN TestColumn9 ELSE NULL END, CASE WHEN TestColumn10 >= 33 THEN TestColumn10 ELSE NULL END, CASE WHEN TestColumn11 <> 44 THEN TestColumn11 ELSE NULL END, CASE WHEN TestColumn12 < 55 THEN TestColumn12 ELSE NULL END, CASE WHEN TestColumn13 <= 66 THEN TestColumn13 ELSE NULL END)";
146+
147+
Assert.That(addIndexSql, Is.EqualTo(expectedSql));
148+
}
149+
150+
/// <summary>
151+
/// Migrator throws if UNIQUE is used with functional expressions.
152+
/// </summary>
153+
[Test]
154+
public void AddIndex_FilterItemsCombinedWithUnique_Throws()
155+
{
156+
// Arrange
157+
const string tableName = "TestTable";
158+
const string columnName1 = "TestColumn1";
159+
const string indexName = "TestIndexName";
160+
161+
Provider.AddTable(tableName,
162+
new Column(columnName1, DbType.Int16)
163+
);
164+
165+
List<FilterItem> filterItems = [
166+
new() { Filter = FilterType.EqualTo, ColumnName = columnName1, Value = 1 },
167+
];
168+
169+
// Act/Assert
170+
Assert.Throws<MigrationException>(() => Provider.AddIndex(tableName,
171+
new Index
172+
{
173+
Name = indexName,
174+
KeyColumns = [
175+
columnName1
176+
],
177+
Unique = true,
178+
FilterItems = filterItems
179+
}));
180+
}
49181
}

src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_AddIndexTests.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ public void AddIndex_FilteredIndexMiscellaneousFilterTypesAndDataTypes_Success()
271271
];
272272

273273
// Act
274-
Provider.AddIndex(tableName,
274+
var addIndexSql = Provider.AddIndex(tableName,
275275
new Index
276276
{
277277
Name = indexName,
@@ -290,7 +290,7 @@ public void AddIndex_FilteredIndexMiscellaneousFilterTypesAndDataTypes_Success()
290290
columnName12,
291291
columnName13
292292
],
293-
Unique = false,
293+
Unique = true,
294294
FilterItems = filterItems
295295
});
296296

@@ -311,5 +311,9 @@ public void AddIndex_FilteredIndexMiscellaneousFilterTypesAndDataTypes_Success()
311311
filteredItemsFromDatabase.Select(x => x.ColumnName.ToLowerInvariant()),
312312
Is.EquivalentTo(filterItems.Select(x => x.ColumnName.ToLowerInvariant()))
313313
);
314+
315+
var expectedSql = "CREATE UNIQUE INDEX TestIndexName ON TestTable (TestColumn1, TestColumn2, TestColumn3, TestColumn4, TestColumn5, TestColumn6, TestColumn7, TestColumn8, TestColumn9, TestColumn10, TestColumn11, TestColumn12, TestColumn13) WHERE TestColumn1 = 1 AND TestColumn2 > 2 AND TestColumn3 >= 2323 AND TestColumn4 <> 3434 AND TestColumn5 <> -3434 AND TestColumn6 < 3434345345 AND TestColumn7 <> 'asdf' AND TestColumn8 = 11 AND TestColumn9 > 22 AND TestColumn10 >= 33 AND TestColumn11 <> 44 AND TestColumn12 < 55 AND TestColumn13 <= 66";
316+
317+
Assert.That(addIndexSql, Is.EqualTo(expectedSql));
314318
}
315319
}

src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_AddIndexTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ public void AddIndex_FilteredIndexSingle_Success()
199199
}
200200

201201
/// <summary>
202-
/// This test is located in the dedicated database type folder not in the base class since partial indexes (Oracle) are not supported in the migrator at this point in time.
202+
/// This test is located in the dedicated database type folder not in the base class since partial indexes (Oracle) using unique are not supported in the migrator at this point in time.
203203
/// </summary>
204204
[Test]
205205
public void AddIndex_FilteredIndexMiscellaneousFilterTypesAndDataTypes_Success()
@@ -274,7 +274,7 @@ public void AddIndex_FilteredIndexMiscellaneousFilterTypesAndDataTypes_Success()
274274
columnName12,
275275
columnName13
276276
],
277-
Unique = false,
277+
Unique = true,
278278
FilterItems = filterItems
279279
});
280280

src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddIndexTests.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ public void AddIndex_FilteredIndexMiscellaneousFilterTypesAndDataTypes_Success()
214214
];
215215

216216
// Act
217-
Provider.AddIndex(tableName,
217+
var addIndexSql = Provider.AddIndex(tableName,
218218
new Index
219219
{
220220
Name = indexName,
@@ -233,7 +233,7 @@ public void AddIndex_FilteredIndexMiscellaneousFilterTypesAndDataTypes_Success()
233233
columnName12,
234234
columnName13
235235
],
236-
Unique = false,
236+
Unique = true,
237237
FilterItems = filterItems
238238
});
239239

@@ -254,6 +254,10 @@ public void AddIndex_FilteredIndexMiscellaneousFilterTypesAndDataTypes_Success()
254254
filteredItemsFromDatabase.Select(x => x.ColumnName.ToLowerInvariant()),
255255
Is.EquivalentTo(filterItems.Select(x => x.ColumnName.ToLowerInvariant()))
256256
);
257+
258+
var expectedSql = "CREATE UNIQUE INDEX TestIndexName ON TestTable (TestColumn1, TestColumn2, TestColumn3, TestColumn4, TestColumn5, TestColumn6, TestColumn7, TestColumn8, TestColumn9, TestColumn10, TestColumn11, TestColumn12, TestColumn13) WHERE TestColumn1 = 1 AND TestColumn2 > 2 AND TestColumn3 >= 2323 AND TestColumn4 <> 3434 AND TestColumn5 <> -3434 AND TestColumn6 < 3434345345 AND TestColumn7 <> 'asdf' AND TestColumn8 = 11 AND TestColumn9 > 22 AND TestColumn10 >= 33 AND TestColumn11 <> 44 AND TestColumn12 < 55 AND TestColumn13 <= 66";
259+
260+
Assert.That(addIndexSql, Is.EqualTo(expectedSql));
257261
}
258262

259263
private string GetCreateIndexSqlString(string indexName)

src/Migrator/Framework/ITransformationProvider.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,11 @@ public interface ITransformationProvider : IDisposable
390390

391391
List<string> ExecuteStringQuery(string sql, params object[] args);
392392

393+
/// <summary>
394+
/// Oracle: The retrieval of filter items is not supported in this migrator. If functional expressions are used: they seem to be stored as separate columns (with generated names).
395+
/// </summary>
396+
/// <param name="table"></param>
397+
/// <returns></returns>
393398
Index[] GetIndexes(string table);
394399

395400
/// <summary>
@@ -719,15 +724,15 @@ IDataReader SelectComplex(IDbCommand cmd, string table, string[] columns, string
719724
/// <param name="databaseName">Name of the database to delete</param>
720725
void DropDatabases(string databaseName);
721726

722-
void AddIndex(string table, Index index);
727+
string AddIndex(string table, Index index);
723728

724729
/// <summary>
725730
/// Add a multi-column index to a table
726731
/// </summary>
727732
/// <param name="name">The name of the index to add.</param>
728733
/// <param name="table">The name of the table that will get the index.</param>
729734
/// <param name="columns">The name of the column or columns that are in the index.</param>
730-
void AddIndex(string name, string table, params string[] columns);
735+
string AddIndex(string name, string table, params string[] columns);
731736

732737
/// <summary>
733738
/// Check to see if an index exists

src/Migrator/Providers/Impl/Oracle/OracleTransformationProvider.cs

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -136,39 +136,86 @@ public override void AddForeignKey(string name, string primaryTable, string[] pr
136136
ExecuteNonQuery(string.Format("ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4})", primaryTable, name, primaryColumnsSql, refTable, refColumnsSql));
137137
}
138138

139-
public override void AddIndex(string table, Index index)
139+
public override string AddIndex(string table, Index index)
140140
{
141141
ValidateIndex(tableName: table, index: index);
142+
var hasFilterItems = index.FilterItems != null && index.FilterItems.Count > 0;
142143

143-
var hasIncludedColumns = index.IncludeColumns != null && index.IncludeColumns.Length > 0;
144-
// Included columns and Clustered indexes are not supported in Oracle. We ignore the values given in the properties silently.
144+
// Oracle does not support included columns and clustered indexes. We ignore the values given in the properties SILENTLY for backwards compatibility.
145+
146+
if (index.Unique && hasFilterItems)
147+
{
148+
throw new MigrationException($"You cannot use unique together with functional expressions in Oracle ({nameof(FilterItem)}).");
149+
}
145150

146151
var name = QuoteConstraintNameIfRequired(index.Name);
147152
table = QuoteTableNameIfRequired(table);
148-
var columns = QuoteColumnNamesIfRequired(index.KeyColumns);
149153

150-
var uniqueString = index.Unique ? "UNIQUE" : null;
151-
var columnsString = $"({string.Join(", ", columns)})";
154+
List<string> singleFilterStrings = [];
152155

153-
if (index.FilterItems != null && index.FilterItems.Count > 0)
156+
157+
if (hasFilterItems)
154158
{
155-
throw new NotSupportedException($"Oracle: This migrator does not support partial indexes for Oracle at this point in time. Please use 'if(Provider is {nameof(OracleTransformationProvider)})'");
159+
// In Oracle functional expressions replace the normal columns so we need to remove them
160+
if (index.KeyColumns != null && index.KeyColumns.Length > 0)
161+
{
162+
var keyColumnsList = index.KeyColumns.ToList();
163+
164+
for (var i = keyColumnsList.Count - 1; i >= 0; i--)
165+
{
166+
if (index.FilterItems.Any(x => keyColumnsList[i].Equals(x.ColumnName, StringComparison.OrdinalIgnoreCase)))
167+
{
168+
keyColumnsList.RemoveAt(i);
169+
}
170+
}
171+
172+
index.KeyColumns = keyColumnsList.ToArray();
173+
}
174+
175+
foreach (var filterItem in index.FilterItems)
176+
{
177+
var comparisonString = _dialect.GetComparisonStringByFilterType(filterItem.Filter);
178+
179+
var filterColumnQuoted = QuoteColumnNameIfRequired(filterItem.ColumnName);
180+
string value = null;
181+
182+
value = filterItem.Value switch
183+
{
184+
bool booleanValue => booleanValue ? "TRUE" : "FALSE",
185+
string stringValue => $"'{stringValue}'",
186+
byte or short or int or long => Convert.ToInt64(filterItem.Value).ToString(),
187+
sbyte or ushort or uint or ulong => Convert.ToUInt64(filterItem.Value).ToString(),
188+
_ => throw new NotImplementedException($"Given type in '{nameof(FilterItem)}' is not implemented. Please file an issue."),
189+
};
190+
191+
var singleFilterString = $"CASE WHEN {filterColumnQuoted} {comparisonString} {value} THEN {filterColumnQuoted} ELSE NULL END";
192+
193+
singleFilterStrings.Add(singleFilterString);
194+
}
156195
}
157196

197+
var mixedColumnNamesAndFilters = QuoteColumnNamesIfRequired(index.KeyColumns).ToList();
198+
mixedColumnNamesAndFilters.AddRange(singleFilterStrings);
199+
var columnNamesAndFiltersString = $"({string.Join(", ", mixedColumnNamesAndFilters)})";
200+
201+
var uniqueString = index.Unique ? "UNIQUE" : null;
202+
158203
List<string> list = [];
159204
list.Add("CREATE");
160205
list.Add(uniqueString);
161206
list.Add("INDEX");
162207
list.Add(name);
163208
list.Add("ON");
164209
list.Add(table);
165-
list.Add(columnsString);
210+
list.Add(columnNamesAndFiltersString);
166211

167212
list = [.. list.Where(x => !string.IsNullOrWhiteSpace(x))];
168213

169214
var sql = string.Join(" ", list);
170215

171216
ExecuteNonQuery(sql);
217+
218+
return sql;
172219
}
173220

174221
private void GuardAgainstMaximumIdentifierLengthForOracle(string name)
@@ -892,8 +939,9 @@ LEFT JOIN
892939
user_constraints c ON i.index_name = c.index_name AND
893940
i.table_name = c.table_name
894941
WHERE
895-
UPPER(i.table_name) = '{table.ToUpperInvariant()}' AND
896-
i.index_type = 'NORMAL'
942+
UPPER(i.table_name) = '{table.ToUpperInvariant()}'
943+
-- AND
944+
-- i.index_type = 'NORMAL'
897945
ORDER BY
898946
i.table_name, i.index_name, ic.column_position";
899947

src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ protected override string GetPrimaryKeyConstraintName(string table)
6060
return reader.Read() ? reader.GetString(0) : null;
6161
}
6262

63-
public override void AddIndex(string table, Index index)
63+
public override string AddIndex(string table, Index index)
6464
{
6565
ValidateIndex(tableName: table, index: index);
6666

@@ -121,6 +121,8 @@ public override void AddIndex(string table, Index index)
121121
var sql = string.Join(" ", list.Where(x => !string.IsNullOrWhiteSpace(x)));
122122

123123
ExecuteNonQuery(sql);
124+
125+
return sql;
124126
}
125127

126128
public override Index[] GetIndexes(string table)

0 commit comments

Comments
 (0)