Skip to content

Commit 2dc5d76

Browse files
JaBistDuNarrischJaBistDuNarrisch
authored andcommitted
Set NOT NULL implicitly for PKs in SQLite but only for non-composite PKs
1 parent 14441c4 commit 2dc5d76

File tree

6 files changed

+126
-7
lines changed

6 files changed

+126
-7
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,11 @@ public void AddColumn_HavingColumnPropertyUniqueAndIndex_RebuildSucceeds()
5959
CollectionAssert.AreEquivalent(indexAfter.KeyColumns, new string[] { propertyName1, propertyName2 });
6060
}
6161

62+
/// <summary>
63+
/// NOT NULL is implicitly set by the migrator for non-composite primary key
64+
/// </summary>
6265
[Test]
63-
public void AddColumn_HavingNullInPrimaryKey_HasNULLAfterAddAnotherColumn()
66+
public void AddColumn_HavingNullInPrimaryKey_HasNotNullAfterAddAnotherColumn()
6467
{
6568
// Arrange/Act
6669
Provider.ExecuteNonQuery("CREATE TABLE Common_Language (LanguageID TEXT PRIMARY KEY)");
@@ -73,7 +76,7 @@ public void AddColumn_HavingNullInPrimaryKey_HasNULLAfterAddAnotherColumn()
7376
var columnProperty = tableInfo.Columns.Single(x => x.Name == "LanguageID").ColumnProperty;
7477

7578
// Assert
76-
Assert.That(script, Does.Contain("LanguageID TEXT NULL PRIMARY KEY"));
79+
Assert.That(script, Does.Contain("LanguageID TEXT NOT NULL PRIMARY KEY"));
7780
}
7881

7982
[Test]

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

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
using System;
12
using System.Data;
3+
using System.Data.SQLite;
24
using System.Threading.Tasks;
35
using DotNetProjects.Migrator.Framework;
46
using DotNetProjects.Migrator.Providers.Impl.SQLite;
@@ -27,7 +29,10 @@ public void AddPrimaryKey_ColumnsInOtherOrderThanInColumnsList_Success()
2729
const string tableName = "TestTable";
2830
const string primaryKeyName = $"PK_{tableName}";
2931

30-
Provider.AddTable(tableName, new Column(columnName1, DbType.String), new Column(columnName2, DbType.Int32), new Column(columnName3, DbType.Int32));
32+
Provider.AddTable(tableName,
33+
new Column(columnName1, DbType.String),
34+
new Column(columnName2, DbType.Int32),
35+
new Column(columnName3, DbType.Int32));
3136

3237
// Act
3338
Provider.AddPrimaryKey(name: primaryKeyName, table: tableName, columns: [columnName3, columnName2]);
@@ -37,4 +42,55 @@ public void AddPrimaryKey_ColumnsInOtherOrderThanInColumnsList_Success()
3742

3843
Assert.That(createTableScript, Does.Contain("PRIMARY KEY (TestColumn3, TestColumn2))"));
3944
}
45+
46+
[Test]
47+
public void AddPrimaryKey_ColumnGuidNonComposite_ThrowsOnDuplicatesAndNulls()
48+
{
49+
const string tableName = "MyTableName";
50+
const string columnName1 = "Column1";
51+
var guid = Guid.NewGuid();
52+
53+
// Arrange/Act
54+
Provider.AddTable(tableName,
55+
new Column(columnName1, DbType.Guid, ColumnProperty.PrimaryKey)
56+
);
57+
58+
Provider.Insert(tableName, [columnName1], [guid]);
59+
Assert.Throws<SQLiteException>(() => Provider.Insert(tableName, [columnName1], [guid]));
60+
Assert.Throws<SQLiteException>(() => Provider.Insert(tableName, [columnName1], [null]));
61+
}
62+
63+
[Test]
64+
public void AddPrimaryKey_ColumnGuidComposite_ThrowsOnDuplicatesAndNulls()
65+
{
66+
// Arrange
67+
const string columnName1 = "TestColumn1";
68+
const string columnName2 = "TestColumn2";
69+
const string tableName = "TestTable";
70+
const string primaryKeyName = $"PK_{tableName}";
71+
var guid = Guid.NewGuid();
72+
var guid2 = Guid.NewGuid();
73+
74+
Provider.AddTable(tableName,
75+
new Column(columnName1, DbType.Guid),
76+
new Column(columnName2, DbType.Guid));
77+
78+
// Act
79+
Provider.AddPrimaryKey(name: primaryKeyName, table: tableName, columns: [columnName1, columnName2]);
80+
81+
// This is a normal SQLite behavior!
82+
// NULL != NULL
83+
// (A, NULL) != (A, NULL)
84+
// Duplicates! You need to set NotNull if you want to prevent it!
85+
Provider.Insert(tableName, [columnName1, columnName2], [guid, null]);
86+
87+
Provider.Insert(tableName, [columnName1, columnName2], [guid, null]);
88+
Provider.Insert(tableName, [columnName1, columnName2], [guid, null]);
89+
90+
Provider.Insert(tableName, [columnName1, columnName2], [null, guid]);
91+
Provider.Insert(tableName, [columnName1, columnName2], [null, guid]);
92+
93+
Provider.Insert(tableName, [columnName1, columnName2], [guid2, guid2]);
94+
Assert.Throws<SQLiteException>(() => Provider.Insert(tableName, [columnName1, columnName2], [guid2, guid2]));
95+
}
4096
}

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

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Data.SQLite;
23
using System.Linq;
34
using System.Threading.Tasks;
@@ -89,7 +90,7 @@ public void AddTable_SinglePrimaryKey_ContainsNull()
8990
var createScript = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(tableName);
9091

9192
// In SQLite an INTEGER PRIMARY KEY column is NOT NULL implicitly (see insert asserts above)
92-
Assert.That(createScript, Is.EqualTo("CREATE TABLE MyTableName (Column1 INTEGER PRIMARY KEY, Column2 INTEGER NOT NULL)"));
93+
Assert.That(createScript, Is.EqualTo("CREATE TABLE MyTableName (Column1 INTEGER NOT NULL PRIMARY KEY, Column2 INTEGER NOT NULL)"));
9394

9495
var sqliteInfo = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(tableName);
9596
Assert.That(sqliteInfo.Columns.First().Name, Is.EqualTo(columnName1));
@@ -124,4 +125,58 @@ public void AddTable_MiscellaneousColumns_Succeeds()
124125
Assert.That(sqliteInfo.Columns.First().Name, Is.EqualTo(columnName1));
125126
Assert.That(sqliteInfo.Columns[1].Name, Is.EqualTo(columnName2));
126127
}
128+
129+
/// <summary>
130+
/// NOT NULL is implicitly set by SQLite
131+
/// </summary>
132+
[Test]
133+
public void AddTable_GuidPrimaryKeyOneColumnPKImplicitlyUsingNotNull_ThrowsOnNullAndOnDuplicates()
134+
{
135+
const string tableName = "MyTableName";
136+
const string columnName1 = "Column1";
137+
var guid = Guid.NewGuid();
138+
139+
// Arrange/Act
140+
Provider.AddTable(tableName,
141+
new Column(columnName1, System.Data.DbType.Guid, ColumnProperty.PrimaryKey)
142+
);
143+
144+
Provider.Insert(tableName, [columnName1], [guid]);
145+
Assert.Throws<SQLiteException>(() => Provider.Insert(tableName, [columnName1], [guid]));
146+
147+
// The migrator sets NotNull on PrimaryKey (non composite) so this line throws.
148+
Assert.Throws<SQLiteException>(() => Provider.Insert(tableName, [columnName1], [null]));
149+
}
150+
151+
/// <summary>
152+
/// Composite PK with Guids
153+
/// </summary>
154+
[Test]
155+
public void AddTable_GuidPrimaryKeyCompositeWithGuid_DoesNotThrowOnDuplicateNULLEntries()
156+
{
157+
const string tableName = "MyTableName";
158+
const string columnName1 = "Column1";
159+
const string columnName2 = "Column2";
160+
var guid = Guid.NewGuid();
161+
var guid2 = Guid.NewGuid();
162+
163+
// Arrange/Act
164+
Provider.AddTable(tableName,
165+
new Column(columnName1, System.Data.DbType.Guid, ColumnProperty.PrimaryKey),
166+
new Column(columnName2, System.Data.DbType.Guid, ColumnProperty.PrimaryKey)
167+
);
168+
169+
// This is a normal SQLite behavior!
170+
// NULL != NULL
171+
// (A, NULL) != (A, NULL)
172+
// Duplicates! You need to set NotNull if you want to prevent it!
173+
Provider.Insert(tableName, [columnName1, columnName2], [guid, null]);
174+
Provider.Insert(tableName, [columnName1, columnName2], [guid, null]);
175+
176+
Provider.Insert(tableName, [columnName1, columnName2], [null, guid]);
177+
Provider.Insert(tableName, [columnName1, columnName2], [null, guid]);
178+
179+
Provider.Insert(tableName, [columnName1, columnName2], [guid2, guid2]);
180+
Assert.Throws<SQLiteException>(() => Provider.Insert(tableName, [columnName1, columnName2], [guid2, guid2]));
181+
}
127182
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ public void AddPrimaryIdentity_Succeeds()
2626
var sql = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(testTableName);
2727

2828
// NOT NULL implicitly set in SQLite
29-
Assert.That(sql, Does.Contain("Color1 INTEGER PRIMARY KEY"));
29+
Assert.That(sql, Does.Contain("Color1 INTEGER NOT NULL PRIMARY KEY"));
3030
}
3131
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,6 +1390,13 @@ public override void AddTable(string name, string engine, params IDbField[] fiel
13901390

13911391
foreach (var column in columns)
13921392
{
1393+
if (!hasCompoundPrimaryKey && column.IsPrimaryKey)
1394+
{
1395+
// We implicitly set NOT NULL for non-composite primary keys like in other RDBMS.
1396+
column.ColumnProperty = column.ColumnProperty.Clear(ColumnProperty.Null);
1397+
column.ColumnProperty = column.ColumnProperty.Set(ColumnProperty.NotNull);
1398+
}
1399+
13931400
if (hasCompoundPrimaryKey && column.IsPrimaryKey)
13941401
{
13951402
// We remove PrimaryKey here and readd it as compound later ("...PRIMARY KEY(column1,column2)");

src/Migrator/Providers/Impl/SqlServer/SqlServerTransformationProvider.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -391,8 +391,6 @@ ORDER BY
391391
var schemaNameOrdinal = reader.GetOrdinal("SchemaName");
392392
var tableNameOrdinal = reader.GetOrdinal("TableName");
393393

394-
395-
396394
while (reader.Read())
397395
{
398396
var indexItem = new IndexItem

0 commit comments

Comments
 (0)