diff --git a/.ci/build-steps.yml b/.ci/build-steps.yml index e81044196..384c6aed7 100644 --- a/.ci/build-steps.yml +++ b/.ci/build-steps.yml @@ -27,58 +27,58 @@ steps: displayName: 'Publish MySqlConnector.Tests' inputs: command: 'publish' - arguments: '-c Release -f net8.0 --no-build tests/MySqlConnector.Tests/MySqlConnector.Tests.csproj' + arguments: '-c Release -f net9.0 --no-build tests/MySqlConnector.Tests/MySqlConnector.Tests.csproj' publishWebProjects: false zipAfterPublish: false - task: PublishPipelineArtifact@0 inputs: - artifactName: 'MySqlConnector.Tests-8.0-$(Agent.OS)' - targetPath: 'artifacts/publish/MySqlConnector.Tests/release_net8.0' + artifactName: 'MySqlConnector.Tests-9.0-$(Agent.OS)' + targetPath: 'artifacts/publish/MySqlConnector.Tests/release_net9.0' - task: DotNetCoreCLI@2 displayName: 'Publish Conformance.Tests' inputs: command: 'publish' - arguments: '-c Release -f net8.0 --no-build tests/Conformance.Tests/Conformance.Tests.csproj' + arguments: '-c Release -f net9.0 --no-build tests/Conformance.Tests/Conformance.Tests.csproj' publishWebProjects: false zipAfterPublish: false - task: PublishPipelineArtifact@0 inputs: - artifactName: 'Conformance.Tests-8.0-$(Agent.OS)' - targetPath: 'artifacts/publish/Conformance.Tests/release_net8.0' + artifactName: 'Conformance.Tests-9.0-$(Agent.OS)' + targetPath: 'artifacts/publish/Conformance.Tests/release_net9.0' - task: DotNetCoreCLI@2 displayName: 'Publish MySqlConnector.DependencyInjection.Tests' inputs: command: 'publish' - arguments: '-c Release -f net8.0 --no-build tests/MySqlConnector.DependencyInjection.Tests/MySqlConnector.DependencyInjection.Tests.csproj' + arguments: '-c Release -f net9.0 --no-build tests/MySqlConnector.DependencyInjection.Tests/MySqlConnector.DependencyInjection.Tests.csproj' publishWebProjects: false zipAfterPublish: false - task: PublishPipelineArtifact@0 inputs: - artifactName: 'MySqlConnector.DependencyInjection.Tests-8.0-$(Agent.OS)' - targetPath: 'artifacts/publish/MySqlConnector.DependencyInjection.Tests/release_net8.0' + artifactName: 'MySqlConnector.DependencyInjection.Tests-9.0-$(Agent.OS)' + targetPath: 'artifacts/publish/MySqlConnector.DependencyInjection.Tests/release_net9.0' - task: DotNetCoreCLI@2 - displayName: 'Publish IntegrationTests (7.0)' + displayName: 'Publish IntegrationTests (9.0)' inputs: command: 'publish' - arguments: '-c Release -f net8.0 --no-build tests/IntegrationTests/IntegrationTests.csproj' + arguments: '-c Release -f net9.0 --no-build tests/IntegrationTests/IntegrationTests.csproj' publishWebProjects: false zipAfterPublish: false - task: PublishPipelineArtifact@0 inputs: - artifactName: 'IntegrationTests-net8.0-$(Agent.OS)' - targetPath: 'artifacts/publish/IntegrationTests/release_net8.0' + artifactName: 'IntegrationTests-net9.0-$(Agent.OS)' + targetPath: 'artifacts/publish/IntegrationTests/release_net9.0' - task: DotNetCoreCLI@2 - displayName: 'Publish IntegrationTests (6.0)' + displayName: 'Publish IntegrationTests (8.0)' inputs: command: 'publish' - arguments: '-c Release -f net6.0 --no-build tests/IntegrationTests/IntegrationTests.csproj' + arguments: '-c Release -f net8.0 --no-build tests/IntegrationTests/IntegrationTests.csproj' publishWebProjects: false zipAfterPublish: false - task: PublishPipelineArtifact@0 inputs: - artifactName: 'IntegrationTests-net6.0-$(Agent.OS)' - targetPath: 'artifacts/publish/IntegrationTests/release_net6.0' + artifactName: 'IntegrationTests-net8.0-$(Agent.OS)' + targetPath: 'artifacts/publish/IntegrationTests/release_net8.0' diff --git a/.ci/conformance-test-steps.yml b/.ci/conformance-test-steps.yml index 02d700623..349663215 100644 --- a/.ci/conformance-test-steps.yml +++ b/.ci/conformance-test-steps.yml @@ -10,14 +10,14 @@ steps: - task: DownloadPipelineArtifact@0 condition: always() inputs: - artifactName: 'Conformance.Tests-8.0-$(Agent.OS)' - targetPath: '$(Build.BinariesDirectory)/8.0' + artifactName: 'Conformance.Tests-9.0-$(Agent.OS)' + targetPath: '$(Build.BinariesDirectory)/9.0' - task: DotNetCoreCLI@2 displayName: 'Conformance Tests' inputs: command: 'custom' custom: 'vstest' - arguments: '$(Build.BinariesDirectory)/8.0/Conformance.Tests.dll /logger:trx' + arguments: '$(Build.BinariesDirectory)/9.0/Conformance.Tests.dll /logger:trx' env: CONNECTION_STRING: ${{ parameters.connectionString }} - task: PublishTestResults@2 diff --git a/.ci/integration-tests-steps.yml b/.ci/integration-tests-steps.yml index 14f8a0cc6..7d7bd5d08 100644 --- a/.ci/integration-tests-steps.yml +++ b/.ci/integration-tests-steps.yml @@ -8,9 +8,9 @@ steps: - bash: ${{ format('.ci/docker-run.sh {0} 3300 {1}', parameters.image, parameters.unsupportedFeatures) }} displayName: 'Start Docker container' - task: UseDotNet@2 - displayName: 'Install .NET 6.0' + displayName: 'Install .NET 8.0' inputs: - version: 6.0.x + version: 8.0.x packageType: runtime - task: UseDotNet@2 displayName: 'Install .NET' @@ -33,19 +33,19 @@ steps: image: ${{ parameters.image }} unsupportedFeatures: ${{ parameters.unsupportedFeatures }} connectionString: 'server=localhost;port=3300;user id=mysqltest;password=test;database=mysqltest;ssl mode=none;UseCompression=True;DefaultCommandTimeout=3600;${{ parameters.connectionStringExtra }}' - platform: 'net6.0' + platform: 'net8.0' description: 'Compression' - template: 'integration-test-steps.yml' parameters: image: ${{ parameters.image }} unsupportedFeatures: ${{ parameters.unsupportedFeatures }} connectionString: 'server=localhost;port=3300;user id=mysqltest;password=test;database=mysqltest;ssl mode=none;DefaultCommandTimeout=3600;${{ parameters.connectionStringExtra }}' - platform: 'net6.0' + platform: 'net9.0' description: 'No SSL' - template: 'integration-test-steps.yml' parameters: image: ${{ parameters.image }} unsupportedFeatures: ${{ parameters.unsupportedFeatures }} connectionString: server=localhost;port=3300;user id=mysqltest;password=test;database=mysqltest;ssl mode=required;DefaultCommandTimeout=3600;certificate file=$(Build.Repository.LocalPath)/.ci/server/certs/ssl-client.pfx;${{ parameters.connectionStringExtra }} - platform: 'net6.0' + platform: 'net9.0' description: 'SSL' diff --git a/.ci/mysqlconnector-tests-steps.yml b/.ci/mysqlconnector-tests-steps.yml index 012b11a0b..ef0e6a095 100644 --- a/.ci/mysqlconnector-tests-steps.yml +++ b/.ci/mysqlconnector-tests-steps.yml @@ -6,7 +6,7 @@ steps: includePreviewVersions: true - task: DownloadPipelineArtifact@0 inputs: - artifactName: 'MySqlConnector.Tests-8.0-$(Agent.OS)' + artifactName: 'MySqlConnector.Tests-9.0-$(Agent.OS)' targetPath: $(System.DefaultWorkingDirectory) - task: DotNetCoreCLI@2 displayName: 'Run MySqlConnector.Tests' @@ -16,7 +16,7 @@ steps: arguments: 'MySqlConnector.Tests.dll /logger:trx' - task: DownloadPipelineArtifact@0 inputs: - artifactName: 'MySqlConnector.DependencyInjection.Tests-8.0-$(Agent.OS)' + artifactName: 'MySqlConnector.DependencyInjection.Tests-9.0-$(Agent.OS)' targetPath: $(System.DefaultWorkingDirectory) - task: DotNetCoreCLI@2 displayName: 'Run MySqlConnector.DependencyInjection.Tests' @@ -28,4 +28,4 @@ steps: inputs: testResultsFormat: VSTest testResultsFiles: '**/*.trx' - testRunTitle: 'MySqlConnector.Tests-8.0-$(Agent.OS)' + testRunTitle: 'MySqlConnector.Tests-9.0-$(Agent.OS)' diff --git a/.ci/test.ps1 b/.ci/test.ps1 index a619c6402..7d595e35e 100644 --- a/.ci/test.ps1 +++ b/.ci/test.ps1 @@ -38,14 +38,14 @@ dotnet test -c Release -f net462 if ($LASTEXITCODE -ne 0){ exit $LASTEXITCODE; } -dotnet test -c Release -f net7.0 +dotnet test -c Release -f net9.0 if ($LASTEXITCODE -ne 0){ exit $LASTEXITCODE; } echo "Executing integration tests with Compression, No SSL" Copy-Item -Force ..\..\.ci\config\config.compression.json config.json -dotnet test -c Release -f net6.0 +dotnet test -c Release -f net8.0 if ($LASTEXITCODE -ne 0){ exit $LASTEXITCODE; } diff --git a/appveyor.yml b/appveyor.yml index c4f4f64c4..ef905dcaa 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,8 +8,8 @@ cache: install: - ps: Invoke-WebRequest -Uri "https://dot.net/v1/dotnet-install.ps1" -OutFile "install-dotnet.ps1" - ps: .\install-dotnet.ps1 -Channel 6.0 -InstallDir "dotnetcli" - - ps: .\install-dotnet.ps1 -Channel 7.0 -InstallDir "dotnetcli" - ps: .\install-dotnet.ps1 -Channel 8.0 -InstallDir "dotnetcli" + - ps: .\install-dotnet.ps1 -Channel 9.0 -InstallDir "dotnetcli" build_script: - dotnet --info before_test: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5e57f867e..fd74685be 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,5 +1,5 @@ variables: - DotNetCoreSdkVersion: '8.x' + DotNetCoreSdkVersion: '9.x' NUGET_PACKAGES: '$(Pipeline.Workspace)/.nuget/packages' jobs: @@ -16,17 +16,6 @@ jobs: vmimage: 'windows-latest' steps: - template: '.ci/build-steps.yml' - - task: DotNetCoreCLI@2 - displayName: 'Publish IntegrationTests (net472)' - inputs: - command: 'publish' - arguments: '-c Release -f net472 tests/IntegrationTests/IntegrationTests.csproj' - publishWebProjects: false - zipAfterPublish: false - - task: PublishPipelineArtifact@0 - inputs: - artifactName: 'IntegrationTests-net472-$(Agent.OS)' - targetPath: 'artifacts/publish/IntegrationTests/release_net472' - job: windows_mysql_data displayName: 'MySql.Data Tests' @@ -41,9 +30,9 @@ jobs: contents: 'config.json' targetFolder: 'tests/IntegrationTests' - task: UseDotNet@2 - displayName: 'Install .NET 7.0' + displayName: 'Install .NET 8.0' inputs: - version: '7.0.x' + version: '8.0.x' - task: UseDotNet@2 displayName: 'Install .NET' inputs: @@ -91,11 +80,6 @@ jobs: vmimage: 'windows-2019' steps: - template: '.ci/install-mysql-windows.yml' - - task: UseDotNet@2 - displayName: 'Install .NET Core 3.1' - inputs: - version: 3.1.x - packageType: runtime - template: '.ci/mysqlconnector-tests-steps.yml' - template: '.ci/conformance-test-steps.yml' parameters: @@ -123,18 +107,18 @@ jobs: displayName: 'Remove target frameworks' inputs: targetType: 'inline' - script: '((Get-Content .\tests\IntegrationTests\IntegrationTests.csproj -Raw) -replace(''.*'', ''net472;net8.0'')) | Set-Content .\tests\IntegrationTests\IntegrationTests.csproj' + script: '((Get-Content .\tests\IntegrationTests\IntegrationTests.csproj -Raw) -replace(''.*'', ''net481;net9.0'')) | Set-Content .\tests\IntegrationTests\IntegrationTests.csproj' - task: DotNetCoreCLI@2 displayName: 'Restore packages' inputs: command: 'restore' - task: DotNetCoreCLI@2 - displayName: 'Integration tests (net472/net8.0)' + displayName: 'Integration tests (net481/net9.0)' inputs: command: 'test' projects: 'tests/IntegrationTests/IntegrationTests.csproj' - arguments: '-c Release --no-restore' - testRunTitle: ${{ format('{0}, $(Agent.OS), {1}, {2}', 'mysql:8.0', 'net472/net8.0', 'No SSL') }} + arguments: '-c Release --no-restore -p:TestTfmsInParallel=false' + testRunTitle: ${{ format('{0}, $(Agent.OS), {1}, {2}', 'mysql:8.0', 'net481/net9.0', 'No SSL') }} env: DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,UnixDomainSocket' DATA__CONNECTIONSTRING: 'server=localhost;port=3306;user id=mysqltest;password=test;database=mysqltest;ssl mode=none;DefaultCommandTimeout=3600;AllowPublicKeyRetrieval=True;UseCompression=True' @@ -146,9 +130,9 @@ jobs: steps: - template: '.ci/install-mysql-windows.yml' - task: UseDotNet@2 - displayName: 'Install .NET 6.0' + displayName: 'Install .NET 8.0' inputs: - version: 6.0.x + version: 8.0.x packageType: runtime - task: UseDotNet@2 displayName: 'Install .NET' @@ -161,18 +145,18 @@ jobs: displayName: 'Remove target frameworks' inputs: targetType: 'inline' - script: '((Get-Content .\tests\IntegrationTests\IntegrationTests.csproj -Raw) -replace(''.*'', ''net6.0'')) | Set-Content .\tests\IntegrationTests\IntegrationTests.csproj' + script: '((Get-Content .\tests\IntegrationTests\IntegrationTests.csproj -Raw) -replace(''.*'', ''net8.0'')) | Set-Content .\tests\IntegrationTests\IntegrationTests.csproj' - task: DotNetCoreCLI@2 displayName: 'Restore packages' inputs: command: 'restore' - task: DotNetCoreCLI@2 - displayName: 'Integration tests (net6.0)' + displayName: 'Integration tests (net8.0)' inputs: command: 'test' projects: 'tests/IntegrationTests/IntegrationTests.csproj' - arguments: '-c Release --no-restore' - testRunTitle: ${{ format('{0}, $(Agent.OS), {1}, {2}', 'mysql:8.0', 'net6.0', 'No SSL') }} + arguments: '-c Release --no-restore -p:TestTfmsInParallel=false' + testRunTitle: ${{ format('{0}, $(Agent.OS), {1}, {2}', 'mysql:8.0', 'net8.0', 'No SSL') }} env: DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,UnixDomainSocket' DATA__CONNECTIONSTRING: 'server=localhost;port=3306;user id=mysqltest;password=test;database=mysqltest;ssl mode=none;DefaultCommandTimeout=3600;AllowPublicKeyRetrieval=True' diff --git a/global.json b/global.json index 3a69170ca..746f46e86 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "8.0" + "version": "9.0" } } diff --git a/src/MySqlConnector/Authentication/AuthenticationPlugins.cs b/src/MySqlConnector/Authentication/AuthenticationPlugins.cs index 025362e0f..92fdf19c7 100644 --- a/src/MySqlConnector/Authentication/AuthenticationPlugins.cs +++ b/src/MySqlConnector/Authentication/AuthenticationPlugins.cs @@ -35,6 +35,10 @@ internal static bool TryGetPlugin(string name, [NotNullWhen(true)] out IAuthenti return s_plugins.TryGetValue(name, out plugin); } +#if NET9_0_OR_GREATER + private static readonly Lock s_lock = new(); +#else private static readonly object s_lock = new(); +#endif private static readonly Dictionary s_plugins = []; } diff --git a/src/MySqlConnector/ColumnReaders/BinaryTimeColumnReader.cs b/src/MySqlConnector/ColumnReaders/BinaryTimeColumnReader.cs index f8ff90a41..ff2b17fff 100644 --- a/src/MySqlConnector/ColumnReaders/BinaryTimeColumnReader.cs +++ b/src/MySqlConnector/ColumnReaders/BinaryTimeColumnReader.cs @@ -28,7 +28,9 @@ public override object ReadValue(ReadOnlySpan data, ColumnDefinitionPayloa microseconds = -microseconds; } -#if NET7_0_OR_GREATER +#if NET9_0_OR_GREATER + return TimeSpan.FromDays(days, hours, minutes, seconds, microseconds: microseconds); +#elif NET7_0_OR_GREATER return new TimeSpan(days, hours, minutes, seconds, microseconds / 1000, microseconds % 1000); #else return new TimeSpan(days, hours, minutes, seconds) + TimeSpan.FromTicks(microseconds * 10); diff --git a/src/MySqlConnector/Core/ILoadBalancer.cs b/src/MySqlConnector/Core/ILoadBalancer.cs index 8e3b106d8..48edbf47d 100644 --- a/src/MySqlConnector/Core/ILoadBalancer.cs +++ b/src/MySqlConnector/Core/ILoadBalancer.cs @@ -27,22 +27,38 @@ internal sealed class RandomLoadBalancer : ILoadBalancer public IReadOnlyList LoadBalance(IReadOnlyList hosts) { #pragma warning disable CA5394 // Do not use insecure randomness - // from https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm +#if NET8_0_OR_GREATER + var shuffled = hosts.ToArray(); + lock (m_lock) + m_random.Shuffle(shuffled); + return shuffled; +#else var shuffled = new List(hosts); + // from https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm for (var i = hosts.Count - 1; i >= 1; i--) { int j; - lock (m_random) + lock (m_lock) j = m_random.Next(i + 1); if (i != j) (shuffled[j], shuffled[i]) = (shuffled[i], shuffled[j]); } return shuffled; +#endif } - private RandomLoadBalancer() => m_random = new(); + private RandomLoadBalancer() + { + m_random = new(); + m_lock = new(); + } private readonly Random m_random; +#if NET9_0_OR_GREATER + private readonly Lock m_lock; +#else + private readonly object m_lock; +#endif } internal sealed class RoundRobinLoadBalancer : ILoadBalancer @@ -63,6 +79,10 @@ public IReadOnlyList LoadBalance(IReadOnlyList hosts) return shuffled; } +#if NET9_0_OR_GREATER + private readonly Lock m_lock; +#else private readonly object m_lock; +#endif private uint m_counter; } diff --git a/src/MySqlConnector/Core/ServerSession.cs b/src/MySqlConnector/Core/ServerSession.cs index 93112e16f..d6d613ed6 100644 --- a/src/MySqlConnector/Core/ServerSession.cs +++ b/src/MySqlConnector/Core/ServerSession.cs @@ -1471,7 +1471,11 @@ private async Task InitSslAsync(ProtocolCapabilities serverCapabilities, Connect { try { +#if NET9_0_OR_GREATER + var certificate = X509CertificateLoader.LoadPkcs12FromFile(cs.CertificateFile, cs.CertificatePassword, X509KeyStorageFlags.MachineKeySet); +#else var certificate = new X509Certificate2(cs.CertificateFile, cs.CertificatePassword, X509KeyStorageFlags.MachineKeySet); +#endif if (!certificate.HasPrivateKey) { certificate.Dispose(); @@ -1543,7 +1547,9 @@ private async Task InitSslAsync(ProtocolCapabilities serverCapabilities, Connect { // load the certificate at this index; note that 'new X509Certificate' stops at the end of the first certificate it loads Log.LoadingCaCertificate(m_logger, Id, index); -#if NET5_0_OR_GREATER +#if NET9_0_OR_GREATER + var caCertificate = X509CertificateLoader.LoadCertificate(certificateBytes.AsSpan(index, (nextIndex == -1 ? certificateBytes.Length : nextIndex) - index)); +#elif NET5_0_OR_GREATER var caCertificate = new X509Certificate2(certificateBytes.AsSpan(index, (nextIndex == -1 ? certificateBytes.Length : nextIndex) - index), default(ReadOnlySpan), X509KeyStorageFlags.MachineKeySet); #else var caCertificate = new X509Certificate2(Utility.ArraySlice(certificateBytes, index, (nextIndex == -1 ? certificateBytes.Length : nextIndex) - index), default(string), X509KeyStorageFlags.MachineKeySet); @@ -1739,7 +1745,11 @@ X509CertificateCollection LoadCertificate(string sslKeyFile, string sslCertifica // Schannel has a bug where ephemeral keys can't be loaded: https://github.com/dotnet/runtime/issues/23749#issuecomment-485947319 // The workaround is to export the key (which may make it "Perphemeral"): https://github.com/dotnet/runtime/issues/23749#issuecomment-739895373 var oldCertificate = m_clientCertificate; +#if NET9_0_OR_GREATER + m_clientCertificate = X509CertificateLoader.LoadPkcs12(m_clientCertificate.Export(X509ContentType.Pkcs12, default(string?)), null); +#else m_clientCertificate = new X509Certificate2(m_clientCertificate.Export(X509ContentType.Pkcs12)); +#endif oldCertificate.Dispose(); } return [m_clientCertificate]; @@ -1810,7 +1820,7 @@ X509CertificateCollection LoadCertificate(string sslKeyFile, string sslCertifica throw new MySqlException("Could not load the client key from " + sslCertificateFile, ex); } #endif - } + } } #if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER @@ -1867,15 +1877,12 @@ private async Task GetRealServerDetailsAsync(IOBehavior ioBehavior, Cancellation // first (and only) row payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); - static void ReadRow(ReadOnlySpan span, out int? connectionId, out ServerVersion? serverVersion) - { - var reader = new ByteArrayReader(span); - var length = reader.ReadLengthEncodedIntegerOrNull(); - connectionId = (length != -1 && Utf8Parser.TryParse(reader.ReadByteString(length), out int id, out _)) ? id : default(int?); - length = reader.ReadLengthEncodedIntegerOrNull(); - serverVersion = length != -1 ? new ServerVersion(reader.ReadByteString(length)) : default; - } - ReadRow(payload.Span, out var connectionId, out var serverVersion); + + var reader = new ByteArrayReader(payload.Span); + var length = reader.ReadLengthEncodedIntegerOrNull(); + var connectionId = (length != -1 && Utf8Parser.TryParse(reader.ReadByteString(length), out int id, out _)) ? id : default(int?); + length = reader.ReadLengthEncodedIntegerOrNull(); + var serverVersion = length != -1 ? new ServerVersion(reader.ReadByteString(length)) : default; // OK/EOF payload payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); diff --git a/src/MySqlConnector/MySqlBulkLoader.cs b/src/MySqlConnector/MySqlBulkLoader.cs index 07f3bbb41..8b2fc8306 100644 --- a/src/MySqlConnector/MySqlBulkLoader.cs +++ b/src/MySqlConnector/MySqlBulkLoader.cs @@ -176,8 +176,7 @@ internal async ValueTask LoadAsync(IOBehavior ioBehavior, CancellationToken { // replace the file name with a sentinel so that we know (when processing the result set) that it's not spoofed by the server var newFileName = GenerateSourceFileName(); - lock (s_lock) - s_sources.Add(newFileName, CreateFileStream(FileName!)); + AddSource(newFileName, CreateFileStream(FileName!)); FileName = newFileName; } } @@ -187,8 +186,7 @@ internal async ValueTask LoadAsync(IOBehavior ioBehavior, CancellationToken throw new InvalidOperationException("Local must be true to use SourceStream, SourceDataTable, or SourceDataReader."); FileName = GenerateSourceFileName(); - lock (s_lock) - s_sources.Add(FileName, Source!); + AddSource(FileName, Source!); } var closeConnection = false; @@ -221,6 +219,12 @@ internal async ValueTask LoadAsync(IOBehavior ioBehavior, CancellationToken if (closeConnection) Connection.Close(); } + + static void AddSource(string name, object source) + { + lock (s_lock) + s_sources.Add(name, source); + } } internal const string SourcePrefix = ":SOURCE:"; @@ -316,6 +320,10 @@ internal static bool TryGetAndRemoveSource(string sourceKey, [NotNullWhen(true)] private static string GenerateSourceFileName() => SourcePrefix + Guid.NewGuid().ToString("N"); +#if NET9_0_OR_GREATER + private static readonly Lock s_lock = new(); +#else private static readonly object s_lock = new(); +#endif private static readonly Dictionary s_sources = []; } diff --git a/src/MySqlConnector/MySqlConnection.cs b/src/MySqlConnector/MySqlConnection.cs index b98c330f7..a037a3eed 100644 --- a/src/MySqlConnector/MySqlConnection.cs +++ b/src/MySqlConnector/MySqlConnection.cs @@ -1265,7 +1265,11 @@ private ConnectionSettings GetConnectionSettings() => private static readonly StateChangeEventArgs s_stateChangeClosedConnecting = new(ConnectionState.Closed, ConnectionState.Connecting); private static readonly StateChangeEventArgs s_stateChangeConnectingOpen = new(ConnectionState.Connecting, ConnectionState.Open); private static readonly StateChangeEventArgs s_stateChangeOpenClosed = new(ConnectionState.Open, ConnectionState.Closed); +#if NET9_0_OR_GREATER + private static readonly Lock s_lock = new(); +#else private static readonly object s_lock = new(); +#endif private static readonly Dictionary> s_transactionConnections = []; private static readonly ReadOnlyMemory[] s_startTransactionPayloads = new ReadOnlyMemory[5 * 3 * 2]; diff --git a/src/MySqlConnector/MySqlConnector.csproj b/src/MySqlConnector/MySqlConnector.csproj index db3ec187d..769a9a5e6 100644 --- a/src/MySqlConnector/MySqlConnector.csproj +++ b/src/MySqlConnector/MySqlConnector.csproj @@ -1,7 +1,7 @@ - net462;net471;net48;netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0 + net462;net471;net48;netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0;net9.0 A truly async MySQL ADO.NET provider, supporting MySQL Server, MariaDB, Amazon Aurora, Azure Database for MySQL, Google Cloud SQL, and more. Copyright 2016–2024 Bradley Grainger Bradley Grainger diff --git a/src/MySqlConnector/Protocol/Serialization/ProtocolUtility.cs b/src/MySqlConnector/Protocol/Serialization/ProtocolUtility.cs index 77e20d081..259f3f963 100644 --- a/src/MySqlConnector/Protocol/Serialization/ProtocolUtility.cs +++ b/src/MySqlConnector/Protocol/Serialization/ProtocolUtility.cs @@ -1,6 +1,5 @@ using System.Buffers; using MySqlConnector.Protocol.Payloads; -using MySqlConnector.Utilities; namespace MySqlConnector.Protocol.Serialization; diff --git a/src/MySqlConnector/Utilities/TimerQueue.cs b/src/MySqlConnector/Utilities/TimerQueue.cs index 5aa05f736..3b120264e 100644 --- a/src/MySqlConnector/Utilities/TimerQueue.cs +++ b/src/MySqlConnector/Utilities/TimerQueue.cs @@ -131,7 +131,11 @@ public Data(uint id, int time, Action action) public Action Action { get; } } +#if NET9_0_OR_GREATER + private readonly Lock m_lock; +#else private readonly object m_lock; +#endif private readonly Timer m_timer; private readonly List m_timeoutActions; private uint m_counter; diff --git a/src/MySqlConnector/Utilities/Utility.cs b/src/MySqlConnector/Utilities/Utility.cs index 63f07c8c6..69f23a5a9 100644 --- a/src/MySqlConnector/Utilities/Utility.cs +++ b/src/MySqlConnector/Utilities/Utility.cs @@ -430,7 +430,9 @@ public static TimeSpan ParseTimeSpan(ReadOnlySpan value) seconds = -seconds; microseconds = -microseconds; } -#if NET7_0_OR_GREATER +#if NET9_0_OR_GREATER + return TimeSpan.FromHours(hours, minutes, seconds, microseconds: microseconds); +#elif NET7_0_OR_GREATER return new TimeSpan(0, hours, minutes, seconds, microseconds / 1000, microseconds % 1000); #else return new TimeSpan(0, hours, minutes, seconds, microseconds / 1000) + TimeSpan.FromTicks(microseconds % 1000 * 10); diff --git a/tests/Conformance.Tests/Conformance.Tests.csproj b/tests/Conformance.Tests/Conformance.Tests.csproj index 246efa5b1..e2a4ca928 100644 --- a/tests/Conformance.Tests/Conformance.Tests.csproj +++ b/tests/Conformance.Tests/Conformance.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 0.1.0 true true diff --git a/tests/IntegrationTests/ClientFactoryTests.cs b/tests/IntegrationTests/ClientFactoryTests.cs index bebd2f5e0..f74017a73 100644 --- a/tests/IntegrationTests/ClientFactoryTests.cs +++ b/tests/IntegrationTests/ClientFactoryTests.cs @@ -51,7 +51,7 @@ public void DbProviderFactoriesGetFactory() #else var providerInvariantName = "MySqlConnector"; #endif -#if !NET462 && !NET472 +#if !NETFRAMEWORK DbProviderFactories.RegisterFactory(providerInvariantName, MySqlConnectorFactory.Instance); #endif var factory = DbProviderFactories.GetFactory(providerInvariantName); diff --git a/tests/IntegrationTests/IntegrationTests.csproj b/tests/IntegrationTests/IntegrationTests.csproj index 55360d60c..b68d6a05a 100644 --- a/tests/IntegrationTests/IntegrationTests.csproj +++ b/tests/IntegrationTests/IntegrationTests.csproj @@ -1,12 +1,12 @@ - net462;net472;net6.0;net8.0 + net462;net481;net6.0;net8.0;net9.0 false - net8.0 + net9.0 MYSQL_DATA $(NoWarn);MSB3246 @@ -61,7 +61,7 @@ - + diff --git a/tests/IntegrationTests/RedirectionTests.cs b/tests/IntegrationTests/RedirectionTests.cs index 892f1eb7f..f5f374eb9 100644 --- a/tests/IntegrationTests/RedirectionTests.cs +++ b/tests/IntegrationTests/RedirectionTests.cs @@ -1,5 +1,4 @@ #if !MYSQL_DATA -using System.Globalization; using System.Net; using System.Net.Sockets; diff --git a/tests/IntegrationTests/SslTests.cs b/tests/IntegrationTests/SslTests.cs index 6c428f386..e33d33917 100644 --- a/tests/IntegrationTests/SslTests.cs +++ b/tests/IntegrationTests/SslTests.cs @@ -64,7 +64,11 @@ public async Task ConnectSslClientCertificateCallback(string certificateFile, st using var connection = new MySqlConnection(csb.ConnectionString); connection.ProvideClientCertificatesCallback = x => { +#if NET9_0_OR_GREATER + x.Add(X509CertificateLoader.LoadPkcs12FromFile(certificateFilePath, certificateFilePassword)); +#else x.Add(new X509Certificate2(certificateFilePath, certificateFilePassword)); +#endif return default; }; @@ -103,7 +107,9 @@ private async Task DoTestSsl(string connectionString) Assert.True(connection.SslIsEncrypted); Assert.True(connection.SslIsSigned); Assert.True(connection.SslIsAuthenticated); +#if !NET9_0_OR_GREATER Assert.True(connection.SslIsMutuallyAuthenticated); +#endif #endif cmd.CommandText = "SHOW SESSION STATUS LIKE 'Ssl_version'"; var sslVersion = (string) await cmd.ExecuteScalarAsync(); @@ -117,7 +123,11 @@ public async Task ConnectSslClientCertificateFromCertificateStore(string certFil // Create a mock of certificate store var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadWrite); +#if NET9_0_OR_GREATER + var certificate = X509CertificateLoader.LoadPkcs12FromFile(Path.Combine(AppConfig.CertsPath, certFile), null); +#else var certificate = new X509Certificate2(Path.Combine(AppConfig.CertsPath, certFile)); +#endif store.Add(certificate); var csb = AppConfig.CreateConnectionStringBuilder(); @@ -133,9 +143,11 @@ public async Task ConnectSslClientCertificateFromCertificateStore(string certFil Assert.True(connection.SslIsEncrypted); Assert.True(connection.SslIsSigned); Assert.True(connection.SslIsAuthenticated); +#if !NET9_0_OR_GREATER Assert.True(connection.SslIsMutuallyAuthenticated); #endif - cmd.CommandText = "SHOW SESSION STATUS LIKE 'Ssl_version'"; +#endif + cmd.CommandText = "SHOW SESSION STATUS LIKE 'Ssl_version'"; var sslVersion = (string) await cmd.ExecuteScalarAsync(); Assert.False(string.IsNullOrWhiteSpace(sslVersion)); } diff --git a/tests/IntegrationTests/Transaction.cs b/tests/IntegrationTests/Transaction.cs index 7a22b96f5..ac33ab5e0 100644 --- a/tests/IntegrationTests/Transaction.cs +++ b/tests/IntegrationTests/Transaction.cs @@ -169,7 +169,7 @@ public async Task ReadWriteTransactionAsync() Assert.Equal(new[] { 1, 2 }, results); } -#if !NET462 && !NET472 +#if !NETFRAMEWORK [Fact] public async Task DbConnectionCommitAsync() { @@ -241,7 +241,7 @@ public async Task RollbackDisposeAsync() Assert.Equal(new int[0], results); } -#if !NET462 && !NET472 +#if !NETFRAMEWORK [Fact] public async Task DbConnectionRollbackAsync() { diff --git a/tests/MySqlConnector.DependencyInjection.Tests/MySqlConnector.DependencyInjection.Tests.csproj b/tests/MySqlConnector.DependencyInjection.Tests/MySqlConnector.DependencyInjection.Tests.csproj index fac726a3a..ebeeef845 100644 --- a/tests/MySqlConnector.DependencyInjection.Tests/MySqlConnector.DependencyInjection.Tests.csproj +++ b/tests/MySqlConnector.DependencyInjection.Tests/MySqlConnector.DependencyInjection.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 true true ..\..\MySqlConnector.snk diff --git a/tests/MySqlConnector.Tests/MySqlConnector.Tests.csproj b/tests/MySqlConnector.Tests/MySqlConnector.Tests.csproj index b7c7f164e..5d14a2cce 100644 --- a/tests/MySqlConnector.Tests/MySqlConnector.Tests.csproj +++ b/tests/MySqlConnector.Tests/MySqlConnector.Tests.csproj @@ -1,7 +1,7 @@ - net481;net8.0 + net481;net9.0 diff --git a/tools/SchemaCollectionGenerator/SchemaCollectionGenerator.csproj b/tools/SchemaCollectionGenerator/SchemaCollectionGenerator.csproj index 73e62aa5e..85c1ed799 100644 --- a/tools/SchemaCollectionGenerator/SchemaCollectionGenerator.csproj +++ b/tools/SchemaCollectionGenerator/SchemaCollectionGenerator.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 enable enable true