diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9213627ec..7de7a822a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -8,7 +8,7 @@ env:
jobs:
ci:
- runs-on: windows-2022
+ runs-on: windows-2025
strategy:
fail-fast: false
matrix:
@@ -17,12 +17,12 @@ jobs:
timeout-minutes: 120
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- - name: .NET 8.0
- uses: actions/setup-dotnet@v4
+ - name: .NET 10.0
+ uses: actions/setup-dotnet@v5
with:
- dotnet-version: 8.0.x
+ dotnet-version: 10.0.x
- name: Build
run: |
diff --git a/README.md b/README.md
index 8949c0d75..7d5f084d1 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,7 @@
* [Downloads](https://github.com/FirebirdSQL/NETProvider/releases)
* [Issue tracker](https://github.com/FirebirdSQL/NETProvider/issues)
* [Development mailing list](https://groups.google.com/forum/#!forum/firebird-net-provider)
+* [](https://deepwiki.com/FirebirdSQL/NETProvider)
## Builds
diff --git a/build.ps1 b/build.ps1
index 9a321609d..308363e27 100644
--- a/build.ps1
+++ b/build.ps1
@@ -20,25 +20,16 @@ function Clean() {
}
function Build() {
- function b($target, $check=$True) {
- dotnet msbuild /t:$target /p:Configuration=$Configuration /p:ContinuousIntegrationBuild=true "$baseDir\src\NETProvider.sln" /v:m /m
- if ($check) {
- Check-ExitCode
- }
- }
- b 'Clean'
- # this sometimes fails on CI
- b 'Restore' $False
- b 'Restore'
- b 'Build'
+ dotnet clean "$baseDir\src\NETProvider.slnx" -c $Configuration -v m
+ dotnet build "$baseDir\src\NETProvider.slnx" -c $Configuration -p:ContinuousIntegrationBuild=true -v m
}
function Versions() {
function v($file) {
return (Get-Item $file).VersionInfo.ProductVersion -replace '(\d+)\.(\d+)\.(\d+)(-[a-z0-9]+)?.*','$1.$2.$3$4'
}
- $script:versionProvider = v $baseDir\src\FirebirdSql.Data.FirebirdClient\bin\$Configuration\net8.0\FirebirdSql.Data.FirebirdClient.dll
- $script:versionEFCore = v $baseDir\src\FirebirdSql.EntityFrameworkCore.Firebird\bin\$Configuration\net8.0\FirebirdSql.EntityFrameworkCore.Firebird.dll
+ $script:versionProvider = v $baseDir\src\FirebirdSql.Data.FirebirdClient\bin\$Configuration\net10.0\FirebirdSql.Data.FirebirdClient.dll
+ $script:versionEFCore = v $baseDir\src\FirebirdSql.EntityFrameworkCore.Firebird\bin\$Configuration\net10.0\FirebirdSql.EntityFrameworkCore.Firebird.dll
$script:versionEF6 = v $baseDir\src\EntityFramework.Firebird\bin\$Configuration\net48\EntityFramework.Firebird.dll
}
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index e01e0d79d..27f7e6a29 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -20,7 +20,7 @@
license.txt
- 2024
+ 2026
diff --git a/src/EntityFramework.Firebird.Tests/EntityFramework.Firebird.Tests.csproj b/src/EntityFramework.Firebird.Tests/EntityFramework.Firebird.Tests.csproj
index 52fbd1394..01672b56e 100644
--- a/src/EntityFramework.Firebird.Tests/EntityFramework.Firebird.Tests.csproj
+++ b/src/EntityFramework.Firebird.Tests/EntityFramework.Firebird.Tests.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
false
false
true
@@ -16,7 +16,7 @@
-
+
diff --git a/src/EntityFramework.Firebird/EntityFramework.Firebird.csproj b/src/EntityFramework.Firebird/EntityFramework.Firebird.csproj
index 7a028340b..12ec50978 100644
--- a/src/EntityFramework.Firebird/EntityFramework.Firebird.csproj
+++ b/src/EntityFramework.Firebird/EntityFramework.Firebird.csproj
@@ -47,6 +47,7 @@
+
@@ -54,10 +55,4 @@
-
-
-
-
-
-
diff --git a/src/FirebirdSql.Data.FirebirdClient.Tests/FbCommandTests.cs b/src/FirebirdSql.Data.FirebirdClient.Tests/FbCommandTests.cs
index 639c746ea..44ba7c151 100644
--- a/src/FirebirdSql.Data.FirebirdClient.Tests/FbCommandTests.cs
+++ b/src/FirebirdSql.Data.FirebirdClient.Tests/FbCommandTests.cs
@@ -647,7 +647,7 @@ public async Task GetCommandExplainedPlanNoPlanTest()
}
[Test]
- public async Task ReadsTimeWithProperPrecision()
+ public async Task ReadsTimeWithProperPrecisionTest()
{
await using (var cmd = Connection.CreateCommand())
{
@@ -658,7 +658,7 @@ public async Task ReadsTimeWithProperPrecision()
}
[Test]
- public async Task PassesTimeSpanWithProperPrecision()
+ public async Task PassesTimeSpanWithProperPrecisionTest()
{
var ts = TimeSpan.FromTicks(14321000);
await using (var cmd = Connection.CreateCommand())
@@ -671,7 +671,7 @@ public async Task PassesTimeSpanWithProperPrecision()
}
[Test]
- public async Task ReadsDateTimeWithProperPrecision()
+ public async Task ReadsDateTimeWithProperPrecisionTest()
{
await using (var cmd = Connection.CreateCommand())
{
@@ -682,7 +682,7 @@ public async Task ReadsDateTimeWithProperPrecision()
}
[Test]
- public async Task PassesDateTimeWithProperPrecision()
+ public async Task PassesDateTimeWithProperPrecisionTest()
{
var dt = new DateTime(635583639614321000);
await using (var cmd = Connection.CreateCommand())
@@ -695,7 +695,41 @@ public async Task PassesDateTimeWithProperPrecision()
}
[Test]
- public async Task ExecuteNonQueryReturnsMinusOneOnNonInsertUpdateDelete()
+ public async Task HighLowSurrogatePassingTest()
+ {
+ await using (var cmd = Connection.CreateCommand())
+ {
+ const string Value = "😊!";
+ cmd.CommandText = "select cast(@value1 as varchar(2) character set utf8), cast(@value2 as char(2) character set utf8) from rdb$database";
+ cmd.Parameters.Add("value1", Value);
+ cmd.Parameters.Add("value2", Value);
+ await using (var reader = await cmd.ExecuteReaderAsync())
+ {
+ await reader.ReadAsync();
+ Assert.AreEqual(Value, reader[0]);
+ Assert.AreEqual(Value, reader[1]);
+ }
+ }
+ }
+
+ [Test]
+ public async Task HighLowSurrogateReadingTest()
+ {
+ await using (var cmd = Connection.CreateCommand())
+ {
+ const string Value = "😊!";
+ cmd.CommandText = "select cast(x'F09F988A21' as varchar(2) character set utf8), cast(x'F09F988A21' as char(2) character set utf8) from rdb$database";
+ await using (var reader = await cmd.ExecuteReaderAsync())
+ {
+ await reader.ReadAsync();
+ Assert.AreEqual(Value, reader[0]);
+ Assert.AreEqual(Value, reader[1]);
+ }
+ }
+ }
+
+ [Test]
+ public async Task ExecuteNonQueryReturnsMinusOneOnNonInsertUpdateDeleteTest()
{
await using (var cmd = Connection.CreateCommand())
{
diff --git a/src/FirebirdSql.Data.FirebirdClient.Tests/FbDataReaderTests.cs b/src/FirebirdSql.Data.FirebirdClient.Tests/FbDataReaderTests.cs
index 1aa2e50e6..c87c804bc 100644
--- a/src/FirebirdSql.Data.FirebirdClient.Tests/FbDataReaderTests.cs
+++ b/src/FirebirdSql.Data.FirebirdClient.Tests/FbDataReaderTests.cs
@@ -313,7 +313,7 @@ public async Task ReadBinaryTest()
{
bytes[i] = (byte)random.Next(byte.MinValue, byte.MaxValue);
}
- var binaryString = $"x'{BitConverter.ToString(bytes).Replace("-", string.Empty)}'";
+ var binaryString = $"x'{Convert.ToHexString(bytes)}'";
await using (var command = new FbCommand($"select {binaryString} from TEST", Connection, transaction))
{
diff --git a/src/FirebirdSql.Data.FirebirdClient.Tests/FirebirdSql.Data.FirebirdClient.Tests.csproj b/src/FirebirdSql.Data.FirebirdClient.Tests/FirebirdSql.Data.FirebirdClient.Tests.csproj
index 1e902480b..ff0b4ae13 100644
--- a/src/FirebirdSql.Data.FirebirdClient.Tests/FirebirdSql.Data.FirebirdClient.Tests.csproj
+++ b/src/FirebirdSql.Data.FirebirdClient.Tests/FirebirdSql.Data.FirebirdClient.Tests.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
false
false
true
diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/ClientFactory.cs b/src/FirebirdSql.Data.FirebirdClient/Client/ClientFactory.cs
index a3869d1b3..c6ff43285 100644
--- a/src/FirebirdSql.Data.FirebirdClient/Client/ClientFactory.cs
+++ b/src/FirebirdSql.Data.FirebirdClient/Client/ClientFactory.cs
@@ -128,7 +128,7 @@ private static ValueTask CreateNativeDatabaseAsync(ConnectionStrin
{
var charset = GetCharset(options);
- return ValueTask2.FromResult(new Native.FesDatabase(options.ClientLibrary, charset, options.PacketSize, options.Dialect));
+ return ValueTask.FromResult(new Native.FesDatabase(options.ClientLibrary, charset, options.PacketSize, options.Dialect));
}
private static ServiceManagerBase CreateManagedServiceManager(ConnectionString options)
@@ -194,7 +194,7 @@ private static ValueTask CreateNativeServiceManagerAsync(Con
{
var charset = GetCharset(options);
- return ValueTask2.FromResult(new Native.FesServiceManager(options.ClientLibrary, charset));
+ return ValueTask.FromResult(new Native.FesServiceManager(options.ClientLibrary, charset));
}
private static Exception UnsupportedProtocolException() => new NotSupportedException("Protocol not supported.");
diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs
index 28d85b36f..17f068a30 100644
--- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs
+++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs
@@ -122,14 +122,14 @@ public async ValueTask ReadAsync(byte[] buffer, int offset, int count, Canc
public void Write(byte[] buffer, int offset, int count)
{
- for (var i = offset; i < count; i++)
+ for (var i = 0; i < count; i++)
_outputBuffer.Enqueue(buffer[offset + i]);
}
public ValueTask WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default)
{
- for (var i = offset; i < count; i++)
+ for (var i = 0; i < count; i++)
_outputBuffer.Enqueue(buffer[offset + i]);
- return ValueTask2.CompletedTask;
+ return ValueTask.CompletedTask;
}
public void Flush()
diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/GdsConnection.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/GdsConnection.cs
index 249f31fb7..cc39ba6b2 100644
--- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/GdsConnection.cs
+++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/GdsConnection.cs
@@ -114,12 +114,7 @@ public async ValueTask ConnectAsync(CancellationToken cancellationToken = defaul
{
using (var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
{
-#if NET48 || NETSTANDARD2_0 || NETSTANDARD2_1
- static Func ConnectAsyncHelper(Socket socket) => (e, ct) => Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, e, null);
-#else
- static Func ConnectAsyncHelper(Socket socket) => (e, ct) => SocketTaskExtensions.ConnectAsync(socket, e, ct).AsTask();
-#endif
- await ConnectAsyncHelper(socket)(endPoint, combinedCts.Token).ConfigureAwait(false);
+ await socket.ConnectAsync(endPoint, combinedCts.Token).ConfigureAwait(false);
}
}
@@ -366,11 +361,7 @@ public void Disconnect()
{
if (_networkStream != null)
{
-#if NET48 || NETSTANDARD2_0
_networkStream.Dispose();
-#else
- _networkStream.Dispose();
-#endif
_networkStream = null;
}
}
@@ -378,12 +369,7 @@ public async ValueTask DisconnectAsync(CancellationToken cancellationToken = def
{
if (_networkStream != null)
{
-#if NET48 || NETSTANDARD2_0
- _networkStream.Dispose();
- await ValueTask2.CompletedTask.ConfigureAwait(false);
-#else
await _networkStream.DisposeAsync().ConfigureAwait(false);
-#endif
_networkStream = null;
}
}
diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Srp/SrpClientBase.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Srp/SrpClientBase.cs
index 21c331687..b84ab5a17 100644
--- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Srp/SrpClientBase.cs
+++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Srp/SrpClientBase.cs
@@ -146,12 +146,12 @@ private static BigInteger GetUserHash(string user, string password, byte[] salt)
private static BigInteger BigIntegerFromByteArray(byte[] b)
{
- return new BigInteger(b.Reverse().Concat(new byte[] { 0 }).ToArray());
+ return new BigInteger(b.AsEnumerable().Reverse().Concat(new byte[] { 0 }).ToArray());
}
private static byte[] BigIntegerToByteArray(BigInteger n)
{
- return n.ToByteArray().Reverse().SkipWhile((e, i) => i == 0 && e == 0).ToArray();
+ return n.ToByteArray().AsEnumerable().Reverse().SkipWhile((e, i) => i == 0 && e == 0).ToArray();
}
private static byte[] ComputeSHA1Hash(params byte[][] ba)
diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsDatabase.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsDatabase.cs
index 6e9e209ba..37f134aa1 100644
--- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsDatabase.cs
+++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsDatabase.cs
@@ -159,7 +159,7 @@ protected virtual void ProcessAttachResponse(GenericResponse response)
protected virtual ValueTask ProcessAttachResponseAsync(GenericResponse response, CancellationToken cancellationToken = default)
{
_handle = response.ObjectHandle;
- return ValueTask2.CompletedTask;
+ return ValueTask.CompletedTask;
}
protected void AfterAttachActions()
@@ -353,7 +353,7 @@ protected void ProcessCreateResponse(GenericResponse response)
protected ValueTask ProcessCreateResponseAsync(GenericResponse response, CancellationToken cancellationToken = default)
{
_handle = response.ObjectHandle;
- return ValueTask2.CompletedTask;
+ return ValueTask.CompletedTask;
}
public override void CreateDatabaseWithTrustedAuth(DatabaseParameterBufferBase dpb, string database, byte[] cryptKey)
@@ -784,7 +784,7 @@ protected virtual void ProcessReleaseObjectResponse(IResponse response)
{ }
protected virtual ValueTask ProcessReleaseObjectResponseAsync(IResponse response, CancellationToken cancellationToken = default)
{
- return ValueTask2.CompletedTask;
+ return ValueTask.CompletedTask;
}
#endregion
diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsServiceManager.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsServiceManager.cs
index d0f7bf057..20fb3b9cb 100644
--- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsServiceManager.cs
+++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsServiceManager.cs
@@ -113,7 +113,7 @@ protected virtual void ProcessAttachResponse(GenericResponse response)
protected virtual ValueTask ProcessAttachResponseAsync(GenericResponse response, CancellationToken cancellationToken = default)
{
Handle = response.ObjectHandle;
- return ValueTask2.CompletedTask;
+ return ValueTask.CompletedTask;
}
public override void Detach()
diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs
index f920c2706..54b5698cc 100644
--- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs
+++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs
@@ -19,6 +19,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FirebirdSql.Data.Common;
@@ -190,7 +191,7 @@ public override ArrayBase CreateArray(ArrayDesc descriptor)
public override ValueTask CreateArrayAsync(ArrayDesc descriptor, CancellationToken cancellationToken = default)
{
var array = new GdsArray(descriptor);
- return ValueTask2.FromResult(array);
+ return ValueTask.FromResult(array);
}
public override ArrayBase CreateArray(string tableName, string fieldName)
@@ -642,7 +643,7 @@ protected ValueTask ProcessInfoSqlResponseAsync(GenericResponse response
{
Debug.Assert(response.Data != null && response.Data.Length > 0);
- return ValueTask2.FromResult(response.Data);
+ return ValueTask.FromResult(response.Data);
}
#endregion
@@ -728,7 +729,7 @@ protected void ProcessFreeResponse(IResponse response)
{ }
protected ValueTask ProcessFreeResponseAsync(IResponse response, CancellationToken cancellationToken = default)
{
- return ValueTask2.CompletedTask;
+ return ValueTask.CompletedTask;
}
#endregion
@@ -757,7 +758,7 @@ protected ValueTask ProcessAllocateResponseAsync(GenericResponse response, Cance
_allRowsFetched = false;
State = StatementState.Allocated;
StatementType = DbStatementType.None;
- return ValueTask2.CompletedTask;
+ return ValueTask.CompletedTask;
}
#endregion
@@ -841,7 +842,7 @@ protected void ProcessExecuteResponse(GenericResponse response)
{ }
protected ValueTask ProcessExecuteResponseAsync(GenericResponse response, CancellationToken cancellationToken = default)
{
- return ValueTask2.CompletedTask;
+ return ValueTask.CompletedTask;
}
protected void ProcessStoredProcedureExecuteResponse(SqlResponse response)
@@ -1246,7 +1247,7 @@ protected void WriteRawParameter(IXdrWriter xdr, DbField field)
else
{
var svalue = field.DbValue.GetString();
- if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.Length > field.CharCount)
+ if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > field.CharCount)
{
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
}
@@ -1271,7 +1272,7 @@ protected void WriteRawParameter(IXdrWriter xdr, DbField field)
else
{
var svalue = field.DbValue.GetString();
- if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.Length > field.CharCount)
+ if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > field.CharCount)
{
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
}
@@ -1394,7 +1395,7 @@ protected async ValueTask WriteRawParameterAsync(IXdrWriter xdr, DbField field,
else
{
var svalue = await field.DbValue.GetStringAsync(cancellationToken).ConfigureAwait(false);
- if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.Length > field.CharCount)
+ if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > field.CharCount)
{
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
}
@@ -1419,7 +1420,7 @@ protected async ValueTask WriteRawParameterAsync(IXdrWriter xdr, DbField field,
else
{
var svalue = await field.DbValue.GetStringAsync(cancellationToken).ConfigureAwait(false);
- if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.Length > field.CharCount)
+ if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > field.CharCount)
{
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
}
@@ -1532,10 +1533,11 @@ protected object ReadRawValue(IXdrReader xdr, DbField field)
else
{
var s = xdr.ReadString(innerCharset, field.Length);
+ var runes = s.EnumerateRunesToChars().ToList();
if ((field.Length % field.Charset.BytesPerCharacter) == 0 &&
- s.Length > field.CharCount)
+ runes.Count > field.CharCount)
{
- return s.Substring(0, field.CharCount);
+ return new string([.. runes.Take(field.CharCount).SelectMany(x => x)]);
}
else
{
@@ -1629,10 +1631,11 @@ protected async ValueTask