Skip to content

Commit c6a1994

Browse files
authored
Merge branch 'FirebirdSQL:master' into fix-issue-1234
2 parents fb7a19c + a61996c commit c6a1994

6 files changed

Lines changed: 482 additions & 193 deletions

File tree

CONTRIBUTING.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22

33
Thanks for considering contributing. Here are some items to consider before starting (in no particular order).
44

5-
* Check [tracker](https://github.com/FirebirdSQL/NETProvider/issues) if you want to start on something.
6-
* Before you start working on something announce your intention on the mailing list (or in comment in tracker).
5+
* Check [issues](https://github.com/FirebirdSQL/NETProvider/issues) if you want to start on something.
6+
* Before you start working on something announce your intention on the mailing list (or comment on the issue).
77
* Issue only complete PRs (no WIPs).
88
* Make sure your PR doesn't contain unneccessary changes (whitespaces, new lines, ...).
9-
* Consider squashing your commits.
109
* Follow existing code formatting/styling (even for new files).
10+
* Disclose AI usage.
1111
* Make sure you swept all corners (i.e. all build configurations are fine, works with all Firebird version, etc.).
1212
* Make sure your changes merge without conflicts.
1313
* Consider backward compatibility.
1414
* Don't be afraid to ask (i.e. backward compatibility).
1515
* Be prepared to do some changes after your PR is reviewed.
1616
* Consider whether your change is benefit for majority of users (opposite to only a small group).
17-
* All contributions are licensed under [_Initial Developer's Public License_](license.txt).
17+
* All contributions are licensed under [_Initial Developer's Public License_](license.txt).

src/FirebirdSql.Data.TestsBase/FbTestsSetup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ private static async Task CreateTables(FbConnection connection, Version serverVe
111111
commandText.Append("VARRAY_FIELD VARCHAR(30) [1:4],");
112112
commandText.Append("BIG_ARRAY INTEGER [1:32767],");
113113
commandText.Append("EXPR_FIELD COMPUTED BY (smallint_field * 1000),");
114-
commandText.Append("CS_FIELD CHAR(1) CHARACTER SET UNICODE_FSS,");
114+
commandText.Append("CS_FIELD CHAR(1) CHARACTER SET UNICODE_FSS COLLATE UNICODE_FSS,");
115115
commandText.Append("UCCHAR_ARRAY CHAR(10) [1:10] CHARACTER SET UNICODE_FSS);");
116116

117117
await using (var command = new FbCommand(commandText.ToString(), connection))

src/FirebirdSql.EntityFrameworkCore.Firebird.Tests/Scaffolding/ScaffoldingTests.cs

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,28 @@
1515

1616
//$Authors = Jiri Cincura (jiri@cincura.net)
1717

18+
using System;
1819
using System.Linq;
1920
using System.Threading.Tasks;
21+
using FirebirdSql.Data.FirebirdClient;
22+
using FirebirdSql.EntityFrameworkCore.Firebird.Metadata;
23+
using FirebirdSql.EntityFrameworkCore.Firebird.Metadata.Internal;
2024
using FirebirdSql.EntityFrameworkCore.Firebird.Scaffolding.Internal;
2125
using Microsoft.EntityFrameworkCore.Scaffolding;
26+
using Microsoft.EntityFrameworkCore.Scaffolding.Metadata;
2227
using NUnit.Framework;
2328

2429
namespace FirebirdSql.EntityFrameworkCore.Firebird.Tests.Scaffolding;
2530
#pragma warning disable EF1001
2631
public class ScaffoldingTests : EntityFrameworkCoreTestsBase
2732
{
33+
public override async Task SetUp()
34+
{
35+
await base.SetUp();
36+
37+
await CreateScaffoldingObjectsAsync();
38+
}
39+
2840
[Test]
2941
public void JustCanRun()
3042
{
@@ -107,6 +119,208 @@ public async Task ReadsCorrectFieldType(string dataType)
107119
Assert.That(column.StoreType, Is.EqualTo(dataType));
108120
}
109121

122+
[Test]
123+
public void CanScaffoldPrimaryKey()
124+
{
125+
var modelFactory = GetModelFactory();
126+
var databaseModel = modelFactory.Create(Connection, new DatabaseModelFactoryOptions());
127+
var testTable = databaseModel.Tables.Where(t => t.Name.Equals("TEST")).First();
128+
129+
Assert.NotNull(testTable.PrimaryKey);
130+
Assert.AreEqual("INT_FIELD", testTable.PrimaryKey.Columns[0].Name);
131+
}
132+
133+
[Test]
134+
public void CanScaffoldGeneratedByIdentities()
135+
{
136+
var modelFactory = GetModelFactory();
137+
var databaseModel = modelFactory.Create(Connection, new DatabaseModelFactoryOptions());
138+
var testTable = databaseModel.Tables.Where(t => t.Name == "SCAFFOLD_TEST").First();
139+
Assert.NotNull(testTable);
140+
141+
var idDefaultColumn = testTable.Columns.Where(c => c.Name == "ID_DEFAULT").First();
142+
Assert.AreEqual(FbIdentityType.GeneratedByDefault, (FbIdentityType)(idDefaultColumn.GetAnnotation(FbAnnotationNames.IdentityType).Value));
143+
if (FbTestsSetup.ServerVersionAtLeast(ServerVersion, new Version(4, 0, 0, 0)))
144+
{
145+
Assert.IsNull(idDefaultColumn.FindAnnotation(FbAnnotationNames.IdentityStart));
146+
Assert.IsNull(idDefaultColumn.FindAnnotation(FbAnnotationNames.IdentityIncrement));
147+
148+
var testTableFirebird4 = databaseModel.Tables.Where(t => t.Name == "SCAFFOLD_NEW_FB4_TYPES").First();
149+
Assert.NotNull(testTableFirebird4);
150+
151+
var idAlwaysColumn = testTableFirebird4.Columns.Where(c => c.Name == "ID_ALWAYS").First();
152+
Assert.AreEqual(FbIdentityType.GeneratedAlways, (FbIdentityType)idAlwaysColumn.GetAnnotation(FbAnnotationNames.IdentityType).Value);
153+
Assert.AreEqual(2, Convert.ToInt32(idAlwaysColumn.GetAnnotation(FbAnnotationNames.IdentityStart).Value));
154+
Assert.AreEqual(3, Convert.ToInt32(idAlwaysColumn.GetAnnotation(FbAnnotationNames.IdentityIncrement).Value));
155+
}
156+
}
157+
158+
[Test]
159+
public void CanScaffoldColumns()
160+
{
161+
var modelFactory = GetModelFactory();
162+
var databaseModel = modelFactory.Create(Connection, new DatabaseModelFactoryOptions());
163+
var testTable = databaseModel.Tables.Where(t => t.Name == "TEST").First();
164+
Assert.NotNull(testTable);
165+
166+
var intColumn = testTable.Columns.Where(c => c.Name == "INT_FIELD").First();
167+
Assert.AreEqual("INTEGER", intColumn.StoreType);
168+
Assert.AreEqual("0", intColumn.DefaultValueSql);
169+
Assert.IsNull(intColumn.FindAnnotation(FbAnnotationNames.IdentityType));
170+
171+
var charColumn = testTable.Columns.Where(c => c.Name == "CHAR_FIELD").First();
172+
Assert.AreEqual("CHAR(30)", charColumn.StoreType);
173+
174+
var varcharColumn = testTable.Columns.Where(c => c.Name == "VARCHAR_FIELD").First();
175+
Assert.AreEqual("VARCHAR(100)", varcharColumn.StoreType);
176+
177+
var numericColumn = testTable.Columns.Where(c => c.Name == "NUMERIC_FIELD").First();
178+
Assert.AreEqual("NUMERIC(15,2)", numericColumn.StoreType);
179+
180+
var decimalColumn = testTable.Columns.Where(c => c.Name == "DECIMAL_FIELD").First();
181+
Assert.AreEqual("DECIMAL(15,2)", decimalColumn.StoreType);
182+
183+
var blobColumn = testTable.Columns.Where(c => c.Name == "BLOB_FIELD").First();
184+
Assert.AreEqual("BLOB SUB_TYPE BINARY", blobColumn.StoreType);
185+
Assert.AreEqual(80, Convert.ToInt32(blobColumn.GetAnnotation(FbAnnotationNames.BlobSegmentSize).Value));
186+
187+
var clobColumn = testTable.Columns.Where(c => c.Name == "CLOB_FIELD").First();
188+
Assert.AreEqual("BLOB SUB_TYPE TEXT", clobColumn.StoreType);
189+
Assert.AreEqual(80, Convert.ToInt32(clobColumn.GetAnnotation(FbAnnotationNames.BlobSegmentSize).Value));
190+
191+
var exprColumn = testTable.Columns.Where(c => c.Name == "EXPR_FIELD").First();
192+
Assert.AreEqual("(smallint_field * 1000)", exprColumn.ComputedColumnSql);
193+
194+
var csColumn = testTable.Columns.Where(c => c.Name == "CS_FIELD").First();
195+
Assert.AreEqual("CHAR(1)", csColumn.StoreType);
196+
Assert.AreEqual("UNICODE_FSS", csColumn.Collation);
197+
Assert.AreEqual("UNICODE_FSS", csColumn.GetAnnotation(FbAnnotationNames.CharacterSet).Value.ToString());
198+
}
199+
200+
[Test]
201+
public void CanScaffoldFirebird4DataTypes()
202+
{
203+
if (!EnsureServerVersionAtLeast(new Version(4, 0, 0, 0)))
204+
return;
205+
206+
var modelFactory = GetModelFactory();
207+
var databaseModel = modelFactory.Create(Connection, new DatabaseModelFactoryOptions());
208+
var testTable = databaseModel.Tables.Where(t => t.Name == "SCAFFOLD_NEW_FB4_TYPES").First();
209+
Assert.NotNull(testTable);
210+
211+
var int128Column = testTable.Columns.Where(c => c.Name == "INT128_FIELD").First();
212+
Assert.AreEqual("INT128", int128Column.StoreType);
213+
214+
var decFloat16Column = testTable.Columns.Where(c => c.Name == "DECFLOAT_16_FIELD").First();
215+
Assert.AreEqual("DECFLOAT(16)", decFloat16Column.StoreType);
216+
217+
var decFloat34Column = testTable.Columns.Where(c => c.Name == "DECFLOAT_34_FIELD").First();
218+
Assert.AreEqual("DECFLOAT(34)", decFloat34Column.StoreType);
219+
220+
var timeWithTimeZoneColumn = testTable.Columns.Where(c => c.Name == "TWTZ_FIELD").First();
221+
Assert.AreEqual("TIME WITH TIME ZONE", timeWithTimeZoneColumn.StoreType);
222+
223+
var timestampWithTimeZoneColumn = testTable.Columns.Where(c => c.Name == "TSWTZ_FIELD").First();
224+
Assert.AreEqual("TIMESTAMP WITH TIME ZONE", timestampWithTimeZoneColumn.StoreType);
225+
}
226+
227+
async Task CreateScaffoldingObjectsAsync()
228+
{
229+
await ExecuteDdlAsync(Connection, "DROP TABLE SCAFFOLD_NEW_FB4_TYPES", true);
230+
231+
await ExecuteDdlAsync(Connection, "DROP TABLE SCAFFOLD_TEST", true);
232+
233+
if (FbTestsSetup.ServerVersionAtLeast(ServerVersion, new Version(4, 0, 0, 0)))
234+
{
235+
await ExecuteDdlAsync(Connection, """
236+
CREATE TABLE SCAFFOLD_TEST (
237+
ID_DEFAULT INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH 1 INCREMENT BY 1)
238+
)
239+
"""
240+
);
241+
242+
await ExecuteDdlAsync(Connection, """
243+
CREATE TABLE SCAFFOLD_NEW_FB4_TYPES (
244+
ID_ALWAYS INTEGER GENERATED ALWAYS AS IDENTITY (START WITH 2 INCREMENT BY 3),
245+
INT128_FIELD INT128,
246+
DECFLOAT_16_FIELD DECFLOAT(16),
247+
DECFLOAT_34_FIELD DECFLOAT(34),
248+
TWTZ_FIELD TIME WITH TIME ZONE,
249+
TSWTZ_FIELD TIMESTAMP WITH TIME ZONE
250+
)
251+
""");
252+
}
253+
else
254+
{
255+
await ExecuteDdlAsync(Connection, """
256+
CREATE TABLE SCAFFOLD_TEST (
257+
ID_DEFAULT INTEGER GENERATED BY DEFAULT AS IDENTITY
258+
)
259+
"""
260+
);
261+
}
262+
}
263+
264+
static async Task ExecuteDdlAsync(FbConnection connection, string ddlScript, bool ignoreErrors = false)
265+
{
266+
try
267+
{
268+
await using var command = new FbCommand(ddlScript, connection);
269+
await command.ExecuteNonQueryAsync();
270+
}
271+
catch when (ignoreErrors)
272+
{
273+
}
274+
}
275+
276+
[Test]
277+
public async Task ExpressionIndexDoesNotBreakScaffolding()
278+
{
279+
var tableName = "TEST_EXPR_IDX";
280+
281+
using var commandTable = Connection.CreateCommand();
282+
commandTable.CommandText = $"recreate table {tableName} (ID INTEGER NOT NULL, DATA VARCHAR(100))";
283+
await commandTable.ExecuteNonQueryAsync();
284+
285+
using var commandIndex = Connection.CreateCommand();
286+
commandIndex.CommandText = $"create index IDX_EXPR on {tableName} computed by (upper(DATA))";
287+
await commandIndex.ExecuteNonQueryAsync();
288+
289+
var modelFactory = GetModelFactory();
290+
var model = modelFactory.Create(Connection.ConnectionString, new DatabaseModelFactoryOptions(new string[] { tableName }));
291+
var table = model.Tables.Single(x => x.Name == tableName);
292+
293+
Assert.That(table.Indexes, Has.None.Matches<Microsoft.EntityFrameworkCore.Scaffolding.Metadata.DatabaseIndex>(x => x.Name == "IDX_EXPR"));
294+
}
295+
296+
[Test]
297+
public async Task RegularIndexScaffoldedAlongsideExpressionIndex()
298+
{
299+
var tableName = "TEST_MIX_IDX";
300+
301+
using var commandTable = Connection.CreateCommand();
302+
commandTable.CommandText = $"recreate table {tableName} (ID INTEGER NOT NULL, DATA VARCHAR(100))";
303+
await commandTable.ExecuteNonQueryAsync();
304+
305+
using var commandRegularIndex = Connection.CreateCommand();
306+
commandRegularIndex.CommandText = $"create index IDX_REGULAR on {tableName} (DATA)";
307+
await commandRegularIndex.ExecuteNonQueryAsync();
308+
309+
using var commandExprIndex = Connection.CreateCommand();
310+
commandExprIndex.CommandText = $"create index IDX_EXPR_MIX on {tableName} computed by (upper(DATA))";
311+
await commandExprIndex.ExecuteNonQueryAsync();
312+
313+
var modelFactory = GetModelFactory();
314+
var model = modelFactory.Create(Connection.ConnectionString, new DatabaseModelFactoryOptions(new string[] { tableName }));
315+
var table = model.Tables.Single(x => x.Name == tableName);
316+
317+
Assert.Multiple(() =>
318+
{
319+
Assert.That(table.Indexes, Has.Some.Matches<Microsoft.EntityFrameworkCore.Scaffolding.Metadata.DatabaseIndex>(x => x.Name == "IDX_REGULAR"));
320+
Assert.That(table.Indexes, Has.None.Matches<Microsoft.EntityFrameworkCore.Scaffolding.Metadata.DatabaseIndex>(x => x.Name == "IDX_EXPR_MIX"));
321+
});
322+
}
323+
110324
static IDatabaseModelFactory GetModelFactory()
111325
{
112326
return new FbDatabaseModelFactory();
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* The contents of this file are subject to the Initial
3+
* Developer's Public License Version 1.0 (the "License");
4+
* you may not use this file except in compliance with the
5+
* License. You may obtain a copy of the License at
6+
* https://github.com/FirebirdSQL/NETProvider/raw/master/license.txt.
7+
*
8+
* Software distributed under the License is distributed on
9+
* an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
10+
* express or implied. See the License for the specific
11+
* language governing rights and limitations under the License.
12+
*
13+
* All Rights Reserved.
14+
*/
15+
16+
//$Authors = Jiri Cincura (jiri@cincura.net)
17+
18+
namespace FirebirdSql.EntityFrameworkCore.Firebird.Metadata;
19+
20+
public enum FbIdentityType
21+
{
22+
GeneratedAlways = 0,
23+
GeneratedByDefault = 1,
24+
}

src/FirebirdSql.EntityFrameworkCore.Firebird/Metadata/Internal/FbAnnotationNames.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,13 @@ public static class FbAnnotationNames
2525
public const string HiLoSequenceSchema = Prefix + nameof(HiLoSequenceSchema);
2626
public const string SequenceName = Prefix + nameof(SequenceName);
2727
public const string SequenceSchema = Prefix + nameof(SequenceSchema);
28-
public const string SequenceNameSuffix = Prefix+ nameof(SequenceNameSuffix);
28+
public const string SequenceNameSuffix = Prefix + nameof(SequenceNameSuffix);
29+
30+
public const string BlobSegmentSize = Prefix + nameof(BlobSegmentSize);
31+
public const string CharacterSet = Prefix + nameof(CharacterSet);
32+
public const string DomainName = Prefix + nameof(DomainName);
33+
34+
public const string IdentityType = Prefix + nameof(IdentityType);
35+
public const string IdentityStart = Prefix + nameof(IdentityStart);
36+
public const string IdentityIncrement = Prefix + nameof(IdentityIncrement);
2937
}

0 commit comments

Comments
 (0)