Skip to content

Commit 5c95ede

Browse files
JaBistDuNarrischJaBistDuNarrisch
authored andcommitted
Added GetCheckConstraints, RemoveForeignKey and much more...
1 parent 722e1f8 commit 5c95ede

File tree

7 files changed

+223
-15
lines changed

7 files changed

+223
-15
lines changed

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ public void ConstraintExist()
131131
public void AddTableWithCompoundPrimaryKey()
132132
{
133133
Provider.AddTable("Test",
134-
new Column("PersonId", DbType.Int32, ColumnProperty.PrimaryKey),
135-
new Column("AddressId", DbType.Int32, ColumnProperty.PrimaryKey)
134+
new Column("PersonId", DbType.Int32, ColumnProperty.PrimaryKey),
135+
new Column("AddressId", DbType.Int32, ColumnProperty.PrimaryKey)
136136
);
137137

138138
Assert.That(Provider.TableExists("Test"), Is.True, "Table doesn't exist");
@@ -142,14 +142,15 @@ public void AddTableWithCompoundPrimaryKey()
142142
[Test]
143143
public void AddTableWithCompoundPrimaryKeyShouldKeepNullForOtherProperties()
144144
{
145-
Provider.AddTable("Test",
146-
new Column("PersonId", DbType.Int32, ColumnProperty.PrimaryKey),
147-
new Column("AddressId", DbType.Int32, ColumnProperty.PrimaryKey),
148-
new Column("Name", DbType.String, 30, ColumnProperty.Null)
149-
);
145+
var testTableName = "Test";
146+
147+
Provider.AddTable(testTableName,
148+
new Column("PersonId", DbType.Int32, ColumnProperty.PrimaryKey),
149+
new Column("AddressId", DbType.Int32, ColumnProperty.PrimaryKey),
150+
new Column("Name", DbType.String, 30, ColumnProperty.Null)
151+
);
150152

151153
Assert.That(Provider.TableExists("Test"), Is.True, "Table doesn't exist");
152-
Assert.That(Provider.PrimaryKeyExists("Test", "PK_Test"), Is.True, "Constraint doesn't exist");
153154

154155
var column = Provider.GetColumnByName("Test", "Name");
155156

src/Migrator.Tests/Providers/SQLite/Base/SQLiteTransformationProviderTestBase.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public async Task SetUpAsync()
2121
Provider = new SQLiteTransformationProvider(new SQLiteDialect(), connectionString, "default", null);
2222
Provider.BeginTransaction();
2323

24+
AddDefaultTable();
25+
2426
await Task.CompletedTask;
2527
}
2628
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System;
2+
using System.Data.SQLite;
3+
using DotNetProjects.Migrator.Framework;
4+
using DotNetProjects.Migrator.Providers.Impl.SQLite;
5+
using Migrator.Tests.Providers.SQLite.Base;
6+
using NUnit.Framework;
7+
8+
namespace Migrator.Tests.Providers.SQLite;
9+
10+
[TestFixture]
11+
[Category("SQLite")]
12+
public class SQLiteTransformationProvider_GetCheckConstraintsTests : SQLiteTransformationProviderTestBase
13+
{
14+
[Test]
15+
public void GetCheckConstraints_AddCheckConstraintsViaAddTable_CreatesTableCorrectly()
16+
{
17+
const string tableName = "MyTableName";
18+
const string columnName = "MyColumnName";
19+
const string checkConstraint1 = "MyCheckConstraint1";
20+
const string checkConstraint2 = "MyCheckConstraint2";
21+
22+
// Arrange/Act
23+
Provider.AddTable(tableName,
24+
new Column(columnName, System.Data.DbType.Int32),
25+
new CheckConstraint(checkConstraint1, $"{columnName} > 10"),
26+
new CheckConstraint(checkConstraint2, $"{columnName} < 100")
27+
);
28+
29+
var checkConstraints = ((SQLiteTransformationProvider)Provider).GetCheckConstraints(tableName);
30+
31+
// Assert
32+
Assert.That(checkConstraints[0].Name, Is.EqualTo(checkConstraint1));
33+
Assert.That(checkConstraints[0].CheckConstraintString, Is.EqualTo($"{columnName} > 10"));
34+
35+
Assert.That(checkConstraints[1].Name, Is.EqualTo(checkConstraint2));
36+
Assert.That(checkConstraints[1].CheckConstraintString, Is.EqualTo($"{columnName} < 100"));
37+
38+
Provider.Insert(tableName, [columnName], [11]);
39+
Assert.Throws<SQLiteException>(() => Provider.Insert(tableName, [columnName], [1]));
40+
Assert.Throws<SQLiteException>(() => Provider.Insert(tableName, [columnName], [200]));
41+
42+
var createScript = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(tableName);
43+
Assert.That(createScript, Is.EqualTo("CREATE TABLE MyTableName (MyColumnName INTEGER NULL, CONSTRAINT MyCheckConstraint1 CHECK (MyColumnName > 10), CONSTRAINT MyCheckConstraint2 CHECK (MyColumnName < 100))"));
44+
}
45+
46+
[Test]
47+
public void CheckForeignKeyIntegrity_IntegrityOk_ReturnsTrue()
48+
{
49+
// Arrange
50+
AddTableWithPrimaryKey();
51+
Provider.ExecuteNonQuery("INSERT INTO Test (Id, name) VALUES (1, 'my name')");
52+
Provider.ExecuteNonQuery("INSERT INTO TestTwo (TestId) VALUES (1)");
53+
Provider.AddForeignKey(name: "FKName", childTable: "TestTwo", childColumn: "TestId", parentTable: "Test", parentColumn: "Id", constraint: ForeignKeyConstraintType.Cascade);
54+
55+
// Act
56+
var result = ((SQLiteTransformationProvider)Provider).CheckForeignKeyIntegrity();
57+
58+
// Assert
59+
Assert.That(result, Is.True);
60+
}
61+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace DotNetProjects.Migrator.Framework;
2+
3+
/// <summary>
4+
/// Currently only used for SQLite
5+
/// </summary>
6+
public class CheckConstraint : IDbField
7+
{
8+
public CheckConstraint()
9+
{ }
10+
11+
public CheckConstraint(string name, string checkConstraintText)
12+
{
13+
CheckConstraintString = checkConstraintText;
14+
Name = name;
15+
}
16+
17+
/// <summary>
18+
/// Gets or sets the CheckConstraintString. Add it without the braces they will be added by the migrator.
19+
/// </summary>
20+
public string CheckConstraintString { get; set; }
21+
22+
/// <summary>
23+
/// Gets or sets the name of the CHECK constraint.
24+
/// </summary>
25+
public string Name { get; set; }
26+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,10 @@ public override Column[] GetColumns(string table)
547547
{
548548
column.MigratorDbType = MigratorDbType.Boolean;
549549
}
550+
else if (dataTypeString == "NCLOB")
551+
{
552+
column.MigratorDbType = MigratorDbType.String;
553+
}
550554
else
551555
{
552556
throw new NotImplementedException();

src/Migrator/Providers/Impl/SQLite/Models/SQLiteTableInfo.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,9 @@ public class SQLiteTableInfo
3434
/// Gets or sets the unique definitions.
3535
/// </summary>
3636
public List<Unique> Uniques { get; set; } = [];
37+
38+
/// <summary>
39+
/// Gets or sets the check constraint definitions.
40+
/// </summary>
41+
public List<CheckConstraint> CheckConstraints { get; set; } = [];
3742
}

src/Migrator/Providers/Impl/SQLite/SQLiteTransformationProvider.cs

Lines changed: 116 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Linq;
88
using System.Text;
99
using System.Text.RegularExpressions;
10+
using System.Xml.Linq;
1011
using ForeignKeyConstraint = DotNetProjects.Migrator.Framework.ForeignKeyConstraint;
1112
using Index = DotNetProjects.Migrator.Framework.Index;
1213

@@ -108,7 +109,7 @@ public string GetSqlCreateTableScript(string table)
108109
{
109110
if (reader.Read())
110111
{
111-
sqlCreateTableScript = (string)reader[0];
112+
sqlCreateTableScript = reader.IsDBNull(0) ? null : (string)reader[0];
112113
}
113114
}
114115

@@ -340,8 +341,10 @@ public DbType ExtractTypeFromColumnDef(string columnDef)
340341

341342
public override void RemoveForeignKey(string table, string name)
342343
{
343-
//Check the impl...
344-
return;
344+
var sqliteTableInfo = GetSQLiteTableInfo(table);
345+
sqliteTableInfo.ForeignKeys.RemoveAll(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
346+
347+
RecreateTable(sqliteTableInfo);
345348
}
346349

347350
public string[] GetCreateIndexSqlStrings(string table)
@@ -404,6 +407,11 @@ public override void RemoveColumn(string tableName, string column)
404407

405408
var sqliteInfoMainTable = GetSQLiteTableInfo(tableName);
406409

410+
if (sqliteInfoMainTable.CheckConstraints.Any(x => x.CheckConstraintString.Equals(column, StringComparison.OrdinalIgnoreCase)))
411+
{
412+
throw new Exception("A check constraint contains the column you want to remove. Remove the check constraint first");
413+
}
414+
407415
if (!sqliteInfoMainTable.ColumnMappings.Any(x => x.OldName == column))
408416
{
409417
throw new MigrationException("Column not found");
@@ -645,19 +653,26 @@ public override void RemoveConstraint(string table, string name)
645653
{
646654
var sqliteTableInfo = GetSQLiteTableInfo(table);
647655
sqliteTableInfo.Uniques.RemoveAll(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
656+
sqliteTableInfo.CheckConstraints.RemoveAll(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
648657

649658
RecreateTable(sqliteTableInfo);
650659
}
651660

652661
public SQLiteTableInfo GetSQLiteTableInfo(string tableName)
653662
{
663+
if (!TableExists(tableName))
664+
{
665+
return null;
666+
}
667+
654668
var sqliteTable = new SQLiteTableInfo
655669
{
656670
TableNameMapping = new MappingInfo { OldName = tableName, NewName = tableName },
657671
Columns = GetColumns(tableName).ToList(),
658672
ForeignKeys = GetForeignKeyConstraints(tableName).ToList(),
659673
Indexes = GetIndexes(tableName).ToList(),
660-
Uniques = GetUniques(tableName).ToList()
674+
Uniques = GetUniques(tableName).ToList(),
675+
CheckConstraints = GetCheckConstraints(tableName)
661676
};
662677

663678
sqliteTable.ColumnMappings = sqliteTable.Columns
@@ -715,6 +730,7 @@ public void RecreateTable(SQLiteTableInfo sqliteTableInfo)
715730
var foreignKeyDbFields = sqliteTableInfo.ForeignKeys.Cast<IDbField>();
716731
var indexDbFields = sqliteTableInfo.Indexes.Cast<IDbField>();
717732
var uniqueDbFields = sqliteTableInfo.Uniques.Cast<IDbField>();
733+
var checkConstraintDbFields = sqliteTableInfo.CheckConstraints.Cast<IDbField>();
718734

719735
var dbFields = columnDbFields.Concat(foreignKeyDbFields)
720736
.Concat(uniqueDbFields)
@@ -848,6 +864,11 @@ public override List<string> GetDatabases()
848864

849865
public override bool ConstraintExists(string table, string name)
850866
{
867+
if (!TableExists(table))
868+
{
869+
throw new Exception($"Table '{table}' does not exist.");
870+
}
871+
851872
var constraintNames = GetConstraints(table);
852873

853874
var exists = constraintNames.Any(x => x.Equals(name, StringComparison.OrdinalIgnoreCase));
@@ -857,6 +878,11 @@ public override bool ConstraintExists(string table, string name)
857878

858879
public override string[] GetConstraints(string table)
859880
{
881+
if (!TableExists(table))
882+
{
883+
throw new Exception($"Table '{table}' does not exist.");
884+
}
885+
860886
var sqliteInfo = GetSQLiteTableInfo(table);
861887

862888
var foreignKeyNames = sqliteInfo.ForeignKeys
@@ -1131,6 +1157,8 @@ public override void AddTable(string name, string engine, params IDbField[] fiel
11311157
stringBuilder.Append(string.Format(", PRIMARY KEY ({0})", string.Join(", ", pks.ToArray())));
11321158
}
11331159

1160+
1161+
// Uniques
11341162
var uniques = fields.Where(x => x is Unique).Cast<Unique>().ToArray();
11351163

11361164
foreach (var u in uniques)
@@ -1148,6 +1176,7 @@ public override void AddTable(string name, string engine, params IDbField[] fiel
11481176
stringBuilder.Append($" UNIQUE ({uniqueColumnsCommaSeparated})");
11491177
}
11501178

1179+
// Foreign keys
11511180
var foreignKeys = fields.Where(x => x is ForeignKeyConstraint).Cast<ForeignKeyConstraint>().ToArray();
11521181

11531182
List<string> foreignKeyStrings = [];
@@ -1166,12 +1195,25 @@ public override void AddTable(string name, string engine, params IDbField[] fiel
11661195
foreignKeyStrings.Add($"CONSTRAINT {fk.Name} FOREIGN KEY ({sourceColumnNamesQuotedString}) REFERENCES {parentTableNameQuoted}({parentColumnNamesQuotedString})");
11671196
}
11681197

1169-
if (foreignKeyStrings.Count != 0)
1198+
if (foreignKeyStrings.Count > 0)
11701199
{
11711200
stringBuilder.Append(", ");
11721201
stringBuilder.Append(string.Join(", ", foreignKeyStrings));
11731202
}
11741203

1204+
// Check Constraints
1205+
var checkConstraints = fields.Where(x => x is CheckConstraint).OfType<CheckConstraint>().ToArray();
1206+
List<string> checkConstraintStrings = [];
1207+
1208+
foreach (var checkConstraint in checkConstraints)
1209+
{
1210+
checkConstraintStrings.Add($"CONSTRAINT {checkConstraint.Name} CHECK ({checkConstraint.CheckConstraintString})");
1211+
}
1212+
1213+
if (checkConstraintStrings.Count > 0)
1214+
{
1215+
stringBuilder.Append($", {string.Join(", ", checkConstraintStrings)}");
1216+
}
11751217

11761218
stringBuilder.Append(')');
11771219

@@ -1251,6 +1293,11 @@ public override void RemoveAllIndexes(string tableName)
12511293

12521294
public List<Unique> GetUniques(string tableName)
12531295
{
1296+
if (!TableExists(tableName))
1297+
{
1298+
throw new Exception($"Table '{tableName}' does not exist.");
1299+
}
1300+
12541301
var regEx = new Regex(@"(?<=,)\s*(CONSTRAINT\s+\w+\s+)?UNIQUE\s*\(\s*[\w\s,]+\s*\)\s*(?=,|\s*\))");
12551302
var regExConstraintName = new Regex(@"(?<=CONSTRAINT\s+)\w+(?=\s+)");
12561303
var regExParenthesis = new Regex(@"(?<=\().+(?=\))");
@@ -1285,10 +1332,20 @@ public List<Unique> GetUniques(string tableName)
12851332

12861333
var createScript = GetSqlCreateTableScript(tableName);
12871334

1288-
var matches = regEx.Matches(createScript).Cast<Match>().Where(x => x.Success).Select(x => x.Value.Trim()).ToList();
1335+
var matches = regEx.Matches(createScript);
1336+
if (matches.Count == 0)
1337+
{
1338+
return [];
1339+
}
1340+
1341+
var constraintNames = matches
1342+
.OfType<Match>()
1343+
.Where(x => x.Success && !string.IsNullOrWhiteSpace(x.Value))
1344+
.Select(x => x.Value.Trim())
1345+
.ToList();
12891346

12901347
// We can only use the ones containing a starting with CONSTRAINT
1291-
var matchesHavingName = matches.Where(x => x.StartsWith("CONSTRAINT")).ToList();
1348+
var matchesHavingName = constraintNames.Where(x => x.StartsWith("CONSTRAINT")).ToList();
12921349

12931350
foreach (var constraintString in matchesHavingName)
12941351
{
@@ -1397,6 +1454,58 @@ public List<PragmaTableInfoItem> GetPragmaTableInfoItems(string tableNameNotQuot
13971454
return pragmaTableInfoItems;
13981455
}
13991456

1457+
public List<CheckConstraint> GetCheckConstraints(string tableName)
1458+
{
1459+
if (!TableExists(tableName))
1460+
{
1461+
throw new Exception($"Table '{tableName}' does not exist.");
1462+
}
1463+
1464+
var checkConstraintRegex = new Regex(@"(?<=,)[^,]+\s+[^,]+check[^,]+(?=[,|\)])", RegexOptions.IgnoreCase);
1465+
var braceContentRegex = new Regex(@"(?<=^\().+(?=\)$)");
1466+
1467+
var script = GetSqlCreateTableScript(tableName);
1468+
1469+
var matches = checkConstraintRegex.Matches(script);
1470+
1471+
if (matches == null)
1472+
{
1473+
return [];
1474+
}
1475+
1476+
var checkStrings = matches.OfType<Match>()
1477+
.Where(x => x.Success)
1478+
.Select(x => x.Value)
1479+
.ToList();
1480+
1481+
List<CheckConstraint> checkConstraints = [];
1482+
1483+
foreach (var checkString in checkStrings)
1484+
{
1485+
var splitted = checkString.Trim().Split(' ')
1486+
.Select(x => x.Trim())
1487+
.ToList();
1488+
1489+
if (!splitted[0].Equals("CONSTRAINT", StringComparison.OrdinalIgnoreCase) || !splitted[2].Equals("CHECK", StringComparison.OrdinalIgnoreCase))
1490+
{
1491+
throw new Exception($"Cannot parse check constraint in table {tableName}");
1492+
}
1493+
1494+
var checkConstraintStringWithBraces = string.Join(" ", splitted.Skip(3)).Trim();
1495+
var checkConstraintString = braceContentRegex.Match(checkConstraintStringWithBraces);
1496+
1497+
var checkConstraint = new CheckConstraint
1498+
{
1499+
Name = splitted[1],
1500+
CheckConstraintString = checkConstraintString.Value
1501+
};
1502+
1503+
checkConstraints.Add(checkConstraint);
1504+
}
1505+
1506+
return checkConstraints;
1507+
}
1508+
14001509
protected override void ConfigureParameterWithValue(IDbDataParameter parameter, int index, object value)
14011510
{
14021511
if (value is ushort)

0 commit comments

Comments
 (0)