Skip to content

Commit 1fbb073

Browse files
authored
Merge pull request #69 from dotnetprojects/Sqlite
Sqlite
2 parents 87e333c + cc80ac2 commit 1fbb073

7 files changed

+182
-26
lines changed

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,21 @@ public void AddForeignKey()
1919
Provider.ExecuteNonQuery("INSERT INTO TestTwo (TestId) VALUES (1)");
2020

2121
// Act
22-
Provider.AddForeignKey(name: "FK name is not supported by SQLite", childTable: "TestTwo", childColumn: "TestId", parentTable: "Test", parentColumn: "Id", constraint: ForeignKeyConstraintType.Cascade);
22+
Provider.AddForeignKey(name: "FKName", childTable: "TestTwo", childColumn: "TestId", parentTable: "Test", parentColumn: "Id", constraint: ForeignKeyConstraintType.Cascade);
2323

2424
// Assert
2525
var foreignKeyConstraints = ((SQLiteTransformationProvider)Provider).GetForeignKeyConstraints("TestTwo");
2626
var tableSQLCreateScript = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript("TestTwo");
2727

28-
Assert.That(foreignKeyConstraints.Single().Name, Is.Null);
28+
Assert.That(foreignKeyConstraints.Single().Name, Is.EqualTo("FKName"));
2929
Assert.That(foreignKeyConstraints.Single().ChildTable, Is.EqualTo("TestTwo"));
3030
Assert.That(foreignKeyConstraints.Single().ParentTable, Is.EqualTo("Test"));
3131
Assert.That(foreignKeyConstraints.Single().ChildColumns.Single(), Is.EqualTo("TestId"));
3232
Assert.That(foreignKeyConstraints.Single().ParentColumns.Single(), Is.EqualTo("Id"));
3333
// Cascade is not supported
3434

3535
Assert.That(tableSQLCreateScript, Does.Contain("CREATE TABLE \"TestTwo\""));
36-
Assert.That(tableSQLCreateScript, Does.Contain(", FOREIGN KEY (TestId) REFERENCES Test(Id))"));
36+
Assert.That(tableSQLCreateScript, Does.Contain(", CONSTRAINT FKName FOREIGN KEY (TestId) REFERENCES Test(Id))"));
3737

3838
var result = ((SQLiteTransformationProvider)Provider).CheckForeignKeyIntegrity();
3939
Assert.That(result, Is.True);
@@ -48,7 +48,7 @@ public void AddForeignKey_RenameParentColumWithForeignKeyAndData_ForeignKeyPoint
4848
Provider.ExecuteNonQuery("INSERT INTO TestTwo (TestId) VALUES (1)");
4949

5050
// Act
51-
Provider.AddForeignKey(name: "FK name is not supported by SQLite", childTable: "TestTwo", childColumn: "TestId", parentTable: "Test", parentColumn: "Id", constraint: ForeignKeyConstraintType.Cascade);
51+
Provider.AddForeignKey(name: "FKName", childTable: "TestTwo", childColumn: "TestId", parentTable: "Test", parentColumn: "Id", constraint: ForeignKeyConstraintType.Cascade);
5252

5353
// Rename column in parent
5454
Provider.RenameColumn("Test", "Id", "IdNew");
@@ -58,7 +58,7 @@ public void AddForeignKey_RenameParentColumWithForeignKeyAndData_ForeignKeyPoint
5858
var tableSQLCreateScript = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript("TestTwo");
5959

6060
Assert.That(tableSQLCreateScript, Does.Contain("CREATE TABLE \"TestTwo\""));
61-
Assert.That(tableSQLCreateScript, Does.Contain(", FOREIGN KEY (TestId) REFERENCES Test(IdNew))"));
61+
Assert.That(tableSQLCreateScript, Does.Contain(", CONSTRAINT FKName FOREIGN KEY (TestId) REFERENCES Test(IdNew))"));
6262
Assert.That(foreignKeyConstraints.Single().ParentColumns.Single(), Is.EqualTo("IdNew"));
6363

6464
var result = ((SQLiteTransformationProvider)Provider).CheckForeignKeyIntegrity();

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public void CheckForeignKeyIntegrity_IntegrityViolated_ReturnsFalse()
1616
AddTableWithPrimaryKey();
1717
Provider.ExecuteNonQuery("INSERT INTO Test (Id, name) VALUES (1, 'my name')");
1818
Provider.ExecuteNonQuery("INSERT INTO TestTwo (TestId) VALUES (44444)");
19-
Provider.AddForeignKey(name: "FK name is not supported by SQLite", childTable: "TestTwo", childColumn: "TestId", parentTable: "Test", parentColumn: "Id", constraint: ForeignKeyConstraintType.Cascade);
19+
Provider.AddForeignKey(name: "FKName", childTable: "TestTwo", childColumn: "TestId", parentTable: "Test", parentColumn: "Id", constraint: ForeignKeyConstraintType.Cascade);
2020

2121
// Act
2222
var result = ((SQLiteTransformationProvider)Provider).CheckForeignKeyIntegrity();
@@ -32,7 +32,7 @@ public void CheckForeignKeyIntegrity_IntegrityOk_ReturnsTrue()
3232
AddTableWithPrimaryKey();
3333
Provider.ExecuteNonQuery("INSERT INTO Test (Id, name) VALUES (1, 'my name')");
3434
Provider.ExecuteNonQuery("INSERT INTO TestTwo (TestId) VALUES (1)");
35-
Provider.AddForeignKey(name: "FK name is not supported by SQLite", childTable: "TestTwo", childColumn: "TestId", parentTable: "Test", parentColumn: "Id", constraint: ForeignKeyConstraintType.Cascade);
35+
Provider.AddForeignKey(name: "FKName", childTable: "TestTwo", childColumn: "TestId", parentTable: "Test", parentColumn: "Id", constraint: ForeignKeyConstraintType.Cascade);
3636

3737
// Act
3838
var result = ((SQLiteTransformationProvider)Provider).CheckForeignKeyIntegrity();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public void GetColumns_AddUniqueWithTwoColumns_NoUniqueOnColumnLevel()
9999
const string tableName = "GetColumnsTest";
100100
Provider.AddTable(tableName, new Column("Bla1", System.Data.DbType.Int32), new Column("Bla2", System.Data.DbType.Int32));
101101

102-
Provider.AddUniqueConstraint("Index name not used in SQLite", tableName, "Bla1", "Bla2");
102+
Provider.AddUniqueConstraint("IndexName", tableName, "Bla1", "Bla2");
103103

104104
// Act
105105
var columns = Provider.GetColumns(tableName);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System.Data;
2+
using System.Linq;
3+
using Migrator.Framework;
4+
using Migrator.Tests.Providers.SQLite.Base;
5+
using NUnit.Framework;
6+
7+
namespace Migrator.Tests.Providers.SQLite;
8+
9+
[TestFixture]
10+
[Category("SQLite")]
11+
public class SQLiteTransformationProvider_GetForeignKeysTests : SQLiteTransformationProviderTestBase
12+
{
13+
[Test]
14+
public void RenameColumn_HavingASingleForeignKeyPointingToTheTargetColumn_SingleColumnForeignKeyIsRemoved()
15+
{
16+
// Arrange
17+
const string parentA = "TableA";
18+
const string parentAProperty1 = "ParentBProperty1";
19+
const string parentB = "TableB";
20+
const string parentBProperty1 = "ParentBProperty1";
21+
const string parentBProperty2 = "ParentBProperty2";
22+
const string child = "TableChild";
23+
const string childColumnFKToParentAProperty1 = "ChildColumnFKToParentAProperty1";
24+
const string childColumnFKToParentBProperty1 = "ChildColumnFKToParentBProperty1";
25+
const string childColumnFKToParentBProperty2 = "ChildColumnFKToParentBProperty2";
26+
const string foreignKeyStringA = "ForeignKeyStringA";
27+
const string foreignKeyStringB = "ForeignKeyStringB";
28+
29+
Provider.AddTable(parentA, new Column(parentAProperty1, DbType.Int32, ColumnProperty.PrimaryKey));
30+
31+
Provider.AddTable(parentB,
32+
new Column(parentBProperty1, DbType.Int32, ColumnProperty.PrimaryKey),
33+
new Column(parentBProperty2, DbType.Int32, ColumnProperty.Unique)
34+
);
35+
36+
Provider.AddTable(child,
37+
new Column("Id", DbType.Int32, ColumnProperty.PrimaryKey),
38+
new Column(childColumnFKToParentAProperty1, DbType.Int32, ColumnProperty.Unique),
39+
new Column(childColumnFKToParentBProperty1, DbType.Int32),
40+
new Column(childColumnFKToParentBProperty2, DbType.Int32)
41+
);
42+
43+
Provider.AddForeignKey(foreignKeyStringA, child, childColumnFKToParentAProperty1, parentA, parentAProperty1);
44+
Provider.AddForeignKey(foreignKeyStringB, child, [childColumnFKToParentBProperty1, childColumnFKToParentBProperty2], parentB, [parentBProperty1, parentBProperty2]);
45+
46+
// Act
47+
var foreignKeyConstraints = Provider.GetForeignKeyConstraints(child);
48+
49+
// Assert
50+
Assert.That(foreignKeyConstraints.Single(x => x.Name == foreignKeyStringA).ChildColumns, Is.EqualTo([childColumnFKToParentAProperty1]));
51+
Assert.That(foreignKeyConstraints.Single(x => x.Name == foreignKeyStringA).ParentColumns, Is.EqualTo([parentAProperty1]));
52+
53+
Assert.That(foreignKeyConstraints.Single(x => x.Name == foreignKeyStringB).ChildColumns, Is.EqualTo([childColumnFKToParentBProperty1, childColumnFKToParentBProperty2]));
54+
Assert.That(foreignKeyConstraints.Single(x => x.Name == foreignKeyStringB).ParentColumns, Is.EqualTo([parentBProperty1, parentBProperty2]));
55+
}
56+
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,11 @@ public void RemoveColumn_HavingASingleForeignKeyPointingToTheTargetColumn_Single
7676
);
7777

7878
Provider.AddTable(childTestTableName, new Column(propertyChildTableName1, DbType.Int32));
79-
Provider.AddForeignKey("Not used in SQLite", childTestTableName, propertyChildTableName1, parentTableName, propertyName1);
79+
Provider.AddForeignKey("FKName1", childTestTableName, propertyChildTableName1, parentTableName, propertyName1);
8080
var script = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(childTestTableName);
8181

8282
Provider.AddTable(childTestTableName2, new Column(propertyChildTableName1, DbType.Int32));
83-
Provider.AddForeignKey(name: "Not used in SQLite", childTable: childTestTableName2, childColumn: propertyChildTableName1, parentTable: parentTableName, parentColumn: propertyName2);
83+
Provider.AddForeignKey(name: "FKName2", childTable: childTestTableName2, childColumn: propertyChildTableName1, parentTable: parentTableName, parentColumn: propertyName2);
8484

8585
var tableInfoBefore = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(parentTableName);
8686
var tableInfoChildBefore = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(childTestTableName);
@@ -161,7 +161,7 @@ public void RemoveColumn_HavingUniqueConstraintWithTwoColumnsOneOfThemTargetColu
161161
new Column(propertyName3, DbType.Int32, ColumnProperty.Unique)
162162
);
163163

164-
Provider.AddUniqueConstraint("Not used in SQLite", testTableName, [propertyName2, propertyName3]);
164+
Provider.AddUniqueConstraint("UniqueConstraintName", testTableName, [propertyName2, propertyName3]);
165165

166166
Provider.AddIndex(indexName, testTableName, [propertyName1, propertyName2]);
167167
var tableInfoBefore = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System.Collections.Generic;
2+
3+
namespace DotNetProjects.Migrator.Providers.Impl.SQLite.Models;
4+
5+
public class ForeignKeyExtract
6+
{
7+
/// <summary>
8+
/// Gets or sets the complete foreign key string - CONSTRAINT MyFKName FOREIGN KEY (asdf, asdf) REFERENCES ParentTable(asdf,asdf)
9+
/// </summary>
10+
public string ForeignKeyString { get; set; }
11+
12+
/// <summary>
13+
/// Gets or sets the foreign key name
14+
/// </summary>
15+
public string ForeignKeyName { get; set; }
16+
17+
/// <summary>
18+
/// Gets or sets the child column names.
19+
/// </summary>
20+
public List<string> ChildColumnNames { get; set; }
21+
22+
/// <summary>
23+
/// Gets or sets the parent column names.
24+
/// </summary>
25+
public List<string> ParentColumnNames { get; set; }
26+
}

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

Lines changed: 89 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Globalization;
99
using System.Linq;
1010
using System.Text;
11+
using System.Text.RegularExpressions;
1112
using ForeignKeyConstraint = DotNetProjects.Migrator.Framework.ForeignKeyConstraint;
1213
using Index = Migrator.Framework.Index;
1314

@@ -16,7 +17,7 @@ namespace DotNetProjects.Migrator.Providers.Impl.SQLite
1617
/// <summary>
1718
/// Summary description for SQLiteTransformationProvider.
1819
/// </summary>
19-
public class SQLiteTransformationProvider : TransformationProvider
20+
public partial class SQLiteTransformationProvider : TransformationProvider
2021
{
2122
private const string IntermediateTableSuffix = "Temp";
2223

@@ -59,7 +60,7 @@ public override void AddForeignKey(
5960
// SQLite does not support FK names
6061
ChildColumns = childColumns,
6162
ChildTable = childTable,
62-
Name = null,
63+
Name = name,
6364
ParentColumns = parentColumns,
6465
ParentTable = parentTable,
6566
};
@@ -117,6 +118,66 @@ public override ForeignKeyConstraint[] GetForeignKeyConstraints(string tableName
117118
foreignKeyConstraints.Add(foreignKeyConstraint);
118119
}
119120

121+
if (foreignKeyConstraints.Count == 0)
122+
{
123+
return [];
124+
}
125+
126+
var createTableScript = GetSqlCreateTableScript(tableName);
127+
var regEx = ForeignKeyRegex();
128+
var matchesCollection = regEx.Matches(createTableScript);
129+
var fkParts = matchesCollection.Cast<Match>().ToList().Where(x => x.Success).Select(x => x.Value).ToList();
130+
131+
if (fkParts.Count != foreignKeyConstraints.Count)
132+
{
133+
throw new Exception($"Cannot extract all foreign keys out of the create table script in SQLite. Did you use a name as foreign key constraint for all constraints in table '{tableName}' in this or older migrations?");
134+
}
135+
136+
List<ForeignKeyExtract> foreignKeyExtracts = [];
137+
138+
foreach (var fkPart in fkParts)
139+
{
140+
var regexParenthesis = ForeignKeyParenthesisRegex();
141+
var parenthesisContents = regexParenthesis.Matches(fkPart).Cast<Match>().Select(x => x.Groups[1].Value).ToList();
142+
143+
if (parenthesisContents.Count != 2)
144+
{
145+
throw new Exception("Cannot extract parenthesis of foreign key constraint");
146+
}
147+
148+
var foreignKeyExtract = new ForeignKeyExtract()
149+
{
150+
ChildColumnNames = parenthesisContents[0].Split(",").Select(x => x.Trim()).ToList(),
151+
ParentColumnNames = parenthesisContents[1].Split(",").Select(x => x.Trim()).ToList(),
152+
};
153+
154+
var foreignKeyConstraintNameRegex = ForeignKeyConstraintNameRegex();
155+
var foreignKeyNameMatch = foreignKeyConstraintNameRegex.Match(fkPart);
156+
157+
if (!foreignKeyNameMatch.Success)
158+
{
159+
throw new Exception("Could not extract the foreign key constraint name");
160+
}
161+
162+
foreignKeyExtract.ForeignKeyName = foreignKeyNameMatch.Groups[1].Value;
163+
164+
foreignKeyExtracts.Add(foreignKeyExtract);
165+
}
166+
167+
foreach (var foreignKeyConstraint in foreignKeyConstraints)
168+
{
169+
foreach (var foreignKeyExtract in foreignKeyExtracts)
170+
{
171+
if (
172+
foreignKeyExtract.ChildColumnNames.SequenceEqual(foreignKeyConstraint.ChildColumns) &&
173+
foreignKeyExtract.ParentColumnNames.SequenceEqual(foreignKeyConstraint.ParentColumns)
174+
)
175+
{
176+
foreignKeyConstraint.Name = foreignKeyExtract.ForeignKeyName;
177+
}
178+
}
179+
}
180+
120181
return foreignKeyConstraints.ToArray();
121182
}
122183

@@ -749,7 +810,7 @@ public override bool ViewExists(string view)
749810

750811
public override List<string> GetDatabases()
751812
{
752-
throw new NotImplementedException();
813+
throw new NotSupportedException("SQLite is a file-based database. You cannot list other databases.");
753814
}
754815

755816
public override bool ConstraintExists(string table, string name)
@@ -995,34 +1056,40 @@ public override void AddTable(string name, string engine, params IDbField[] fiel
9951056

9961057
foreach (var u in uniques)
9971058
{
998-
// var nm = "";
1059+
if (!string.IsNullOrEmpty(u.Name))
1060+
{
1061+
stringBuilder.Append($" CONSTRAINT {u.Name}");
1062+
}
9991063

1000-
// if (!string.IsNullOrEmpty(u.Name))
1001-
// {
1002-
// nm = string.Format(" CONSTRAINT {0}", u.Name);
1003-
// }
10041064
var uniqueColumnsCommaSeparated = string.Join(", ", u.KeyColumns);
10051065
stringBuilder.Append($", UNIQUE ({uniqueColumnsCommaSeparated})");
10061066
}
10071067

10081068
var foreignKeys = fields.Where(x => x is ForeignKeyConstraint).Cast<ForeignKeyConstraint>().ToArray();
10091069

1070+
List<string> foreignKeyStrings = [];
1071+
10101072
foreach (var fk in foreignKeys)
10111073
{
1012-
// Since in SQLite the foreign key name can be given as
1013-
// CONSTRAINT <FK name>
1014-
// but not being stored in any way hence not being retrievable using foreign_key_list
1015-
// we leave it out in the following string.
1016-
10171074
var sourceColumnNamesQuotedString = string.Join(", ", fk.ChildColumns.Select(QuoteColumnNameIfRequired));
10181075
var parentColumnNamesQuotedString = string.Join(", ", fk.ParentColumns.Select(QuoteColumnNameIfRequired));
10191076
var parentTableNameQuoted = QuoteTableNameIfRequired(fk.ParentTable);
10201077

1021-
var foreignKeyString = $", FOREIGN KEY ({sourceColumnNamesQuotedString}) REFERENCES {parentTableNameQuoted}({parentColumnNamesQuotedString})";
1078+
if (string.IsNullOrWhiteSpace(fk.Name))
1079+
{
1080+
throw new Exception("No foreign key constraint name given");
1081+
}
1082+
1083+
foreignKeyStrings.Add($"CONSTRAINT {fk.Name} FOREIGN KEY ({sourceColumnNamesQuotedString}) REFERENCES {parentTableNameQuoted}({parentColumnNamesQuotedString})");
1084+
}
10221085

1023-
stringBuilder.Append(foreignKeyString);
1086+
if (foreignKeyStrings.Count != 0)
1087+
{
1088+
stringBuilder.Append(", ");
1089+
stringBuilder.Append(string.Join(", ", foreignKeyStrings));
10241090
}
10251091

1092+
10261093
stringBuilder.Append(")");
10271094

10281095
ExecuteNonQuery(stringBuilder.ToString());
@@ -1230,5 +1297,12 @@ protected override void ConfigureParameterWithValue(IDbDataParameter parameter,
12301297
base.ConfigureParameterWithValue(parameter, index, value);
12311298
}
12321299
}
1300+
1301+
[GeneratedRegex(@"CONSTRAINT\s+\w+\s+FOREIGN\s+KEY\s*\([^)]+\)\s+REFERENCES\s+\w+\s*\([^)]+\)")]
1302+
private static partial Regex ForeignKeyRegex();
1303+
[GeneratedRegex(@"\(([^)]+)\)")]
1304+
private static partial Regex ForeignKeyParenthesisRegex();
1305+
[GeneratedRegex(@"CONSTRAINT\s+(\w+)\s+FOREIGN\s+KEY")]
1306+
private static partial Regex ForeignKeyConstraintNameRegex();
12331307
}
12341308
}

0 commit comments

Comments
 (0)