Skip to content

Commit ed2593c

Browse files
Ja bist du narrischJa bist du narrisch
authored andcommitted
Prepared foreign key extraction - mixture of sys tables and string extraction
1 parent f2a0110 commit ed2593c

File tree

3 files changed

+157
-15
lines changed

3 files changed

+157
-15
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using System.Data;
2+
using System.Linq;
3+
using DotNetProjects.Migrator.Framework;
4+
using DotNetProjects.Migrator.Providers.Impl.SQLite;
5+
using Migrator.Framework;
6+
using Migrator.Tests.Providers.SQLite.Base;
7+
using NUnit.Framework;
8+
9+
namespace Migrator.Tests.Providers.SQLite;
10+
11+
[TestFixture]
12+
[Category("SQLite")]
13+
public class SQLiteTransformationProvider_GetForeignKeysTests : SQLiteTransformationProviderTestBase
14+
{
15+
[Test]
16+
public void RenameColumn_HavingASingleForeignKeyPointingToTheTargetColumn_SingleColumnForeignKeyIsRemoved()
17+
{
18+
// Arrange
19+
const string parentA = "TableA";
20+
const string parentAProperty1 = "ParentBProperty1";
21+
const string parentB = "TableB";
22+
const string parentBProperty1 = "ParentBProperty1";
23+
const string parentBProperty2 = "ParentBProperty2";
24+
const string child = "TableChild";
25+
const string childColumnFKToParentAProperty1 = "ChildColumnFKToParentAProperty1";
26+
const string childColumnFKToParentBProperty1 = "ChildColumnFKToParentBProperty1";
27+
const string childColumnFKToParentBProperty2 = "ChildColumnFKToParentBProperty2";
28+
const string foreignKeyStringA = "ForeignKeyStringA";
29+
const string foreignKeyStringB = "ForeignKeyStringB";
30+
31+
Provider.AddTable(parentA, new Column(parentAProperty1, DbType.Int32, ColumnProperty.PrimaryKey));
32+
33+
Provider.AddTable(parentB,
34+
new Column(parentBProperty1, DbType.Int32, ColumnProperty.PrimaryKey),
35+
new Column(parentBProperty2, DbType.Int32, ColumnProperty.Unique)
36+
);
37+
38+
Provider.AddTable(child,
39+
new Column("Id", DbType.Int32, ColumnProperty.PrimaryKey),
40+
new Column(childColumnFKToParentAProperty1, DbType.Int32, ColumnProperty.Unique),
41+
new Column(childColumnFKToParentBProperty1, DbType.Int32),
42+
new Column(childColumnFKToParentBProperty2, DbType.Int32)
43+
);
44+
45+
Provider.AddForeignKey(foreignKeyStringA, child, childColumnFKToParentAProperty1, parentA, parentAProperty1);
46+
Provider.AddForeignKey(foreignKeyStringB, child, [childColumnFKToParentBProperty1, childColumnFKToParentBProperty2], parentB, [parentBProperty1, parentBProperty2]);
47+
48+
// Act
49+
var foreignKeyConstraints = Provider.GetForeignKeyConstraints(child);
50+
51+
// var tableInfoLevel2Before = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(tableNameLevel2);
52+
// var tableInfoLevel3Before = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(tableNameLevel3);
53+
54+
// Provider.ExecuteNonQuery($"INSERT INTO {tableNameLevel1} ({propertyId}) VALUES (1)");
55+
// Provider.ExecuteNonQuery($"INSERT INTO {tableNameLevel1} ({propertyId}) VALUES (2)");
56+
// Provider.ExecuteNonQuery($"INSERT INTO {tableNameLevel2} ({propertyId}, {propertyLevel1Id}) VALUES (1, 1)");
57+
// Provider.ExecuteNonQuery($"INSERT INTO {tableNameLevel3} ({propertyId}, {propertyLevel2Id}) VALUES (1, 1)");
58+
59+
// // Act
60+
// Provider.RenameColumn(tableNameLevel2, propertyId, propertyIdRenamed);
61+
// Provider.RenameColumn(tableNameLevel2, propertyLevel1Id, propertyLevel1IdRenamed);
62+
63+
// // Assert
64+
// Provider.ExecuteNonQuery($"INSERT INTO {tableNameLevel2} ({propertyIdRenamed}, {propertyLevel1IdRenamed}) VALUES (2,2)");
65+
// using var command = Provider.GetCommand();
66+
67+
// using var reader = Provider.ExecuteQuery(command, $"SELECT COUNT(*) as Count from {tableNameLevel2}");
68+
// reader.Read();
69+
// var count = reader.GetInt32(reader.GetOrdinal("Count"));
70+
// Assert.That(count, Is.EqualTo(2));
71+
72+
// var tableInfoLevel2After = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(tableNameLevel2);
73+
74+
// Assert.That(tableInfoLevel2Before.Columns.Single(x => x.Name == propertyId).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.True);
75+
// Assert.That(tableInfoLevel2Before.Columns.Single(x => x.Name == propertyLevel1Id).ColumnProperty.HasFlag(ColumnProperty.Unique), Is.True);
76+
// Assert.That(tableInfoLevel2Before.ForeignKeys.Single().ChildColumns.Single(), Is.EqualTo(propertyLevel1Id));
77+
78+
// Assert.That(tableInfoLevel2After.Columns.FirstOrDefault(x => x.Name == propertyId), Is.Null);
79+
// Assert.That(tableInfoLevel2After.Columns.FirstOrDefault(x => x.Name == propertyLevel1Id), Is.Null);
80+
// Assert.That(tableInfoLevel2After.Columns.FirstOrDefault(x => x.Name == propertyIdRenamed), Is.Not.Null);
81+
// Assert.That(tableInfoLevel2After.Columns.FirstOrDefault(x => x.Name == propertyLevel1IdRenamed), Is.Not.Null);
82+
// Assert.That(tableInfoLevel2After.ForeignKeys.Single().ChildColumns.Single(), Is.EqualTo(propertyLevel1IdRenamed));
83+
84+
// var valid = ((SQLiteTransformationProvider)Provider).CheckForeignKeyIntegrity();
85+
// Assert.That(valid, Is.True);
86+
}
87+
}
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: 44 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,23 @@ public override ForeignKeyConstraint[] GetForeignKeyConstraints(string tableName
117118
foreignKeyConstraints.Add(foreignKeyConstraint);
118119
}
119120

121+
var createTableScript = GetSqlCreateTableScript(tableName);
122+
var regEx = ForeignKeyRegex();
123+
var matchesCollection = regEx.Matches(createTableScript);
124+
var matches = matchesCollection.Cast<Match>().ToList().Where(x => x.Success).Select(x => x.Value).ToList();
125+
126+
if (matches.Count != foreignKeyConstraints.Count)
127+
{
128+
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}?");
129+
}
130+
131+
foreach (var foreignKeyConstraint in foreignKeyConstraints)
132+
{
133+
var regexParenthesis = ForeignKeyParenthesisRegex();
134+
regexParenthesis.Matches()
135+
}
136+
137+
120138
return foreignKeyConstraints.ToArray();
121139
}
122140

@@ -749,7 +767,7 @@ public override bool ViewExists(string view)
749767

750768
public override List<string> GetDatabases()
751769
{
752-
throw new NotImplementedException();
770+
throw new NotSupportedException("SQLite is a file-based database. You cannot list other databases.");
753771
}
754772

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

9961014
foreach (var u in uniques)
9971015
{
998-
// var nm = "";
1016+
if (!string.IsNullOrEmpty(u.Name))
1017+
{
1018+
stringBuilder.Append($" CONSTRAINT {u.Name}");
1019+
}
9991020

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

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

1027+
List<string> foreignKeyStrings = [];
1028+
10101029
foreach (var fk in foreignKeys)
10111030
{
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-
10171031
var sourceColumnNamesQuotedString = string.Join(", ", fk.ChildColumns.Select(QuoteColumnNameIfRequired));
10181032
var parentColumnNamesQuotedString = string.Join(", ", fk.ParentColumns.Select(QuoteColumnNameIfRequired));
10191033
var parentTableNameQuoted = QuoteTableNameIfRequired(fk.ParentTable);
10201034

1021-
var foreignKeyString = $", FOREIGN KEY ({sourceColumnNamesQuotedString}) REFERENCES {parentTableNameQuoted}({parentColumnNamesQuotedString})";
1035+
if (string.IsNullOrWhiteSpace(fk.Name))
1036+
{
1037+
throw new Exception("No foreign key constraint name given");
1038+
}
1039+
1040+
foreignKeyStrings.Add($"CONSTRAINT {fk.Name} FOREIGN KEY ({sourceColumnNamesQuotedString}) REFERENCES {parentTableNameQuoted}({parentColumnNamesQuotedString})");
1041+
}
10221042

1023-
stringBuilder.Append(foreignKeyString);
1043+
if (foreignKeyStrings.Count != 0)
1044+
{
1045+
stringBuilder.Append(", ");
1046+
stringBuilder.Append(string.Join(", ", foreignKeyStrings));
10241047
}
10251048

1049+
10261050
stringBuilder.Append(")");
10271051

10281052
ExecuteNonQuery(stringBuilder.ToString());
@@ -1230,5 +1254,10 @@ protected override void ConfigureParameterWithValue(IDbDataParameter parameter,
12301254
base.ConfigureParameterWithValue(parameter, index, value);
12311255
}
12321256
}
1257+
1258+
[GeneratedRegex(@"CONSTRAINT\s+\w+\s+FOREIGN\s+KEY\s*\([^)]+\)\s+REFERENCES\s+\w+\s*\([^)]+\)")]
1259+
private static partial Regex ForeignKeyRegex();
1260+
[GeneratedRegex(@"\(([^)]+)\)")]
1261+
private static partial Regex ForeignKeyParenthesisRegex();
12331262
}
12341263
}

0 commit comments

Comments
 (0)