Skip to content

Commit 521a65e

Browse files
Ja bist du narrischJa bist du narrisch
authored andcommitted
Updated GetForeignKeyConstraints in TransformationProvider (tested for Postgre)
1 parent 0c119d4 commit 521a65e

File tree

4 files changed

+112
-18
lines changed

4 files changed

+112
-18
lines changed

src/Migrator.Tests/Providers/Base/TransformationProviderConstraintBase.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Data;
2+
using System.Linq;
23
using Migrator.Framework;
34
using NUnit.Framework;
45

@@ -151,4 +152,36 @@ public void AddTableWithCompoundPrimaryKeyShouldKeepNullForOtherProperties()
151152
Assert.That(column, Is.Not.Null);
152153
Assert.That((column.ColumnProperty & ColumnProperty.Null) == ColumnProperty.Null, Is.True);
153154
}
155+
156+
[Test]
157+
public void GetForeignKeyConstraints_SingleColumn_Success()
158+
{
159+
// Arrange
160+
const string fkName = "MyForeignKey";
161+
const string childTableName = "ChildTable";
162+
const string parentTableName = "ParentTable";
163+
const string idColumn = "Id";
164+
const string parentIdColumn = "ParentId";
165+
166+
Provider.AddTable(parentTableName,
167+
new Column(idColumn, DbType.Int32, ColumnProperty.PrimaryKey)
168+
);
169+
170+
Provider.AddTable(childTableName,
171+
new Column(idColumn, DbType.Int32, ColumnProperty.PrimaryKey),
172+
new Column(parentIdColumn, DbType.Int32)
173+
);
174+
175+
Provider.AddForeignKey(fkName, childTableName, parentIdColumn, parentTableName, idColumn);
176+
177+
var foreignKeyConstraints = Provider.GetForeignKeyConstraints(childTableName);
178+
179+
var resultSingle = foreignKeyConstraints.Single();
180+
181+
Assert.That(resultSingle.Name.ToLowerInvariant(), Is.EqualTo(fkName.ToLowerInvariant()));
182+
Assert.That(resultSingle.ChildTable.ToLowerInvariant(), Is.EqualTo(childTableName.ToLowerInvariant()));
183+
Assert.That(resultSingle.ParentTable.ToLowerInvariant(), Is.EqualTo(parentTableName.ToLowerInvariant()));
184+
Assert.That(resultSingle.ChildColumns.Select(x => x.ToLowerInvariant()).Single(), Is.EqualTo(parentIdColumn.ToLowerInvariant()));
185+
Assert.That(resultSingle.ParentColumns.Select(x => x.ToLowerInvariant()).Single(), Is.EqualTo(idColumn.ToLowerInvariant()));
186+
}
154187
}

src/Migrator/Framework/ITransformationProvider.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,12 @@ public interface ITransformationProvider : IDisposable
423423
/// <returns>The names of all the tables.</returns>
424424
string[] GetTables();
425425

426+
/// <summary>
427+
/// Get all foreign keys by the given table name.
428+
/// ATTENTION: For Postgre SQL the result will be lower case if the names were not quoted on table creation of on FK creation!
429+
/// </summary>
430+
/// <param name="table"></param>
431+
/// <returns></returns>
426432
ForeignKeyConstraint[] GetForeignKeyConstraints(string table);
427433

428434
/// <summary>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace DotNetProjects.Migrator.Providers.Models;
2+
3+
public class ForeignKeyConstraintItem
4+
{
5+
public string SchemaName { get; set; }
6+
public string ForeignKeyName { get; set; }
7+
public string ChildTableName { get; set; }
8+
public string ChildColumnName { get; set; }
9+
public string ParentTableName { get; set; }
10+
public string ParentColumnName { get; set; }
11+
}

src/Migrator/Providers/TransformationProvider.cs

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#endregion
1313

1414
using DotNetProjects.Migrator.Framework;
15+
using DotNetProjects.Migrator.Providers.Models;
1516
using Migrator.Framework;
1617
using Migrator.Framework.Loggers;
1718
using Migrator.Framework.SchemaBuilder;
@@ -143,31 +144,71 @@ public virtual Column[] GetColumns(string table)
143144
public virtual ForeignKeyConstraint[] GetForeignKeyConstraints(string table)
144145
{
145146
var constraints = new List<ForeignKeyConstraint>();
147+
var sb = new StringBuilder();
148+
sb.AppendLine("SELECT");
149+
sb.AppendLine(" tc.CONSTRAINT_NAME AS FK_KEY,");
150+
sb.AppendLine(" tc.TABLE_SCHEMA,");
151+
sb.AppendLine(" tc.TABLE_NAME AS CHILD_TABLE,");
152+
sb.AppendLine(" kcu.COLUMN_NAME AS CHILD_COLUMN,");
153+
sb.AppendLine(" ccu.TABLE_NAME AS PARENT_TABLE,");
154+
sb.AppendLine(" ccu.COLUMN_NAME AS PARENT_COLUMN");
155+
sb.AppendLine("FROM ");
156+
sb.AppendLine(" INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc ");
157+
sb.AppendLine("JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE as kcu");
158+
sb.AppendLine(" ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND tc.TABLE_SCHEMA = kcu.TABLE_SCHEMA");
159+
sb.AppendLine("JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS ccu");
160+
sb.AppendLine(" ON tc.CONSTRAINT_NAME = ccu.CONSTRAINT_NAME AND tc.TABLE_SCHEMA = ccu.TABLE_SCHEMA");
161+
sb.AppendLine($"WHERE LOWER(tc.TABLE_NAME) = LOWER('{table}') AND tc.CONSTRAINT_TYPE = 'FOREIGN KEY'");
162+
sb.AppendLine("ORDER BY kcu.ORDINAL_POSITION");
163+
164+
var sql = sb.ToString();
165+
List<ForeignKeyConstraintItem> foreignKeyConstraintItems = [];
166+
146167
using (var cmd = CreateCommand())
147-
using (
148-
var reader =
149-
// TODO:
150-
// In this statement the naming of alias PK is misleading since INFORMATION_SCHEMA.TABLE_CONSTRAINTS (alias PK) is the child
151-
// while INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS (alias C) is the parent
152-
ExecuteQuery(
153-
cmd, string.Format("SELECT K_Table = FK.TABLE_NAME, FK_Column = CU.COLUMN_NAME, PK_Table = PK.TABLE_NAME, PK_Column = PT.COLUMN_NAME, Constraint_Name = C.CONSTRAINT_NAME FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS C INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS FK ON C.CONSTRAINT_NAME = FK.CONSTRAINT_NAME INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK ON C.UNIQUE_CONSTRAINT_NAME = PK.CONSTRAINT_NAME INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE CU ON C.CONSTRAINT_NAME = CU.CONSTRAINT_NAME INNER JOIN ( SELECT i1.TABLE_NAME, i2.COLUMN_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS i1 INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE i2 ON i1.CONSTRAINT_NAME = i2.CONSTRAINT_NAME WHERE i1.CONSTRAINT_TYPE = 'PRIMARY KEY' ) PT ON PT.TABLE_NAME = PK.TABLE_NAME WHERE FK.table_name = '{0}'", table)))
168+
using (var reader = ExecuteQuery(cmd, sql))
154169
{
155170
while (reader.Read())
156171
{
157-
var constraint = new ForeignKeyConstraint
172+
var constraintItem = new ForeignKeyConstraintItem
158173
{
159-
Name = reader.GetString(4),
160-
ParentTable = reader.GetString(0),
161-
ParentColumns = [reader.GetString(1)],
162-
ChildTable = reader.GetString(2),
163-
ChildColumns = [reader.GetString(3)]
174+
SchemaName = reader.GetString(reader.GetOrdinal("TABLE_SCHEMA")),
175+
ForeignKeyName = reader.GetString(reader.GetOrdinal("FK_KEY")),
176+
ChildTableName = reader.GetString(reader.GetOrdinal("CHILD_TABLE")),
177+
ChildColumnName = reader.GetString(reader.GetOrdinal("CHILD_COLUMN")),
178+
ParentTableName = reader.GetString(reader.GetOrdinal("PARENT_TABLE")),
179+
ParentColumnName = reader.GetString(reader.GetOrdinal("PARENT_COLUMN"))
164180
};
165181

166-
constraints.Add(constraint);
182+
foreignKeyConstraintItems.Add(constraintItem);
167183
}
168184
}
169185

170-
return constraints.ToArray();
186+
var schemaChildTableGroups = foreignKeyConstraintItems.GroupBy(x => new { x.SchemaName, x.ChildTableName }).Count();
187+
188+
if (schemaChildTableGroups > 1)
189+
{
190+
throw new MigrationException($"Duplicates found (grouping by schema name and child table name). Since we do not offer schemas in '{nameof(GetForeignKeyConstraints)}' at this moment in time we cannot filter your target schema. Your database use the same table name in different schemas.");
191+
}
192+
193+
var groups = foreignKeyConstraintItems.GroupBy(x => x.ForeignKeyName);
194+
195+
foreach (var group in groups)
196+
{
197+
var first = group.First();
198+
199+
var foreignKeyConstraint = new ForeignKeyConstraint
200+
{
201+
Name = first.ForeignKeyName,
202+
ParentTable = first.ParentTableName,
203+
ParentColumns = [.. group.Select(x => x.ParentColumnName).Distinct()],
204+
ChildTable = first.ChildTableName,
205+
ChildColumns = [.. group.Select(x => x.ChildColumnName).Distinct()]
206+
};
207+
208+
constraints.Add(foreignKeyConstraint);
209+
}
210+
211+
return [.. constraints];
171212
}
172213

173214
public virtual string[] GetConstraints(string table)
@@ -1754,20 +1795,23 @@ public virtual void AddTable(string table, string engine, string columns)
17541795
{
17551796
table = _dialect.TableNameNeedsQuote ? _dialect.Quote(table) : table;
17561797
var sqlCreate = string.Format("CREATE TABLE {0} ({1})", table, columns);
1798+
17571799
ExecuteNonQuery(sqlCreate);
17581800
}
17591801

17601802
public virtual List<string> GetPrimaryKeys(IEnumerable<Column> columns)
17611803
{
1762-
var pks = new List<string>();
1804+
var primaryKeys = new List<string>();
1805+
17631806
foreach (var col in columns)
17641807
{
17651808
if (col.IsPrimaryKey)
17661809
{
1767-
pks.Add(col.Name);
1810+
primaryKeys.Add(col.Name);
17681811
}
17691812
}
1770-
return pks;
1813+
1814+
return primaryKeys;
17711815
}
17721816

17731817
public virtual void AddColumnDefaultValue(string table, string column, object defaultValue)

0 commit comments

Comments
 (0)