diff --git a/.github/workflows/SideBySide/config-ssl.json b/.github/workflows/SideBySide/config-ssl.json
index 79fa0ef22..81650eaba 100644
--- a/.github/workflows/SideBySide/config-ssl.json
+++ b/.github/workflows/SideBySide/config-ssl.json
@@ -1,7 +1,7 @@
{
"Data": {
"ConnectionString": "server=SINGLESTORE_HOST;user id=SQL_USER_NAME;password=SQL_USER_PASSWORD;port=3306;database=singlestoretest;sslmode=Required;certificate file=../../../../.ci/server/certs/ssl-client.pfx;",
- "UnsupportedFeatures": "CachingSha2Password,Ed25519,QueryAttributes,Tls11,Tls13,UuidToBin,UnixDomainSocket,Sha256Password,GlobalLog,Redirection,CancelSleepSuccessfully,TlsFingerprintValidation",
+ "UnsupportedFeatures": "CachingSha2Password,Ed25519,QueryAttributes,Tls11,Tls13,UuidToBin,UnixDomainSocket,Sha256Password,GlobalLog,Redirection,CancelSleepSuccessfully,TlsFingerprintValidation,ParsecAuthentication",
"CertificatesPath": "../../../../.ci/server/certs"
}
}
diff --git a/.github/workflows/SideBySide/config.json b/.github/workflows/SideBySide/config.json
index 4b6d5cb8c..972386fee 100644
--- a/.github/workflows/SideBySide/config.json
+++ b/.github/workflows/SideBySide/config.json
@@ -1,7 +1,7 @@
{
"Data": {
- "ConnectionString": "server=SINGLESTORE_HOST;user id=SQL_USER_NAME;password=SQL_USER_PASSWORD;port=3306;database=singlestoretest;sslmode=None",
- "UnsupportedFeatures": "CachingSha2Password,Ed25519,QueryAttributes,Tls11,Tls13,UuidToBin,UnixDomainSocket,Sha256Password,GlobalLog,Redirection,CancelSleepSuccessfully,TlsFingerprintValidation",
+ "ConnectionString": "server=SINGLESTORE_HOST;user id=SQL_USER_NAME;password=SQL_USER_PASSWORD;port=3306;database=singlestoretest;sslmode=disabled;",
+ "UnsupportedFeatures": "CachingSha2Password,Ed25519,QueryAttributes,Tls11,Tls13,UuidToBin,UnixDomainSocket,Sha256Password,GlobalLog,Redirection,CancelSleepSuccessfully,TlsFingerprintValidation,ParsecAuthentication",
"ManagedService": true
}
}
diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml
index 73ea2f35d..29476f662 100644
--- a/.github/workflows/config.yml
+++ b/.github/workflows/config.yml
@@ -1,11 +1,19 @@
name: SingleStore .NET Connector
-on: [push]
+on:
+ pull_request:
+ types: [opened, synchronize, reopened]
+ push:
+ branches: [master]
+ tags:
+ - 'v*'
+ workflow_call:
+ workflow_dispatch:
env:
- DOTNET_VERSION: 9.0.303
- TARGET_FRAMEWORK: net9.0
- CONNECTOR_VERSION: 1.3.0
+ DOTNET_VERSION: 10.0.x
+ TARGET_FRAMEWORK: net10.0
+ CONNECTOR_VERSION: 1.4.0
LICENSE_KEY: ${{ secrets.LICENSE_KEY }}
SQL_USER_PASSWORD: ${{ secrets.SQL_USER_PASSWORD }}
S2MS_API_KEY: ${{ secrets.S2MS_API_KEY }}
@@ -51,13 +59,13 @@ jobs:
test-ubuntu:
name: ${{ matrix.name }}
- runs-on: ubuntu-22.04
needs: build-matrix
+ runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.build-matrix.outputs.matrix) }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Remove unnecessary pre-installed toolchains for free disk spaces
run: |
@@ -77,18 +85,16 @@ jobs:
df -h
- name: Set up .NET
- uses: actions/setup-dotnet@v4
+ uses: actions/setup-dotnet@v5
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Install dependencies
run: |
sudo apt-get update
- sudo apt-get install -y apt-transport-https
- sudo apt-get install -y mariadb-client-core-10.6
- sudo apt-get install -y mariadb-client-10.6
+ sudo apt-get install -y mariadb-client-core
+ sudo apt-get install -y mariadb-client
sudo apt-get update
- sudo apt-get install -y dotnet-sdk-9.0
dotnet --info
- name: Start SingleStore Cluster
@@ -138,18 +144,19 @@ jobs:
dotnet test -f ${{ env.TARGET_FRAMEWORK }} -c Release --no-build
cd ../../
+
test-windows:
runs-on: windows-latest
strategy:
fail-fast: false
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Install Python dependencies
run: pip install singlestoredb
- name: Install .NET
- uses: actions/setup-dotnet@v4
+ uses: actions/setup-dotnet@v5
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
@@ -161,7 +168,7 @@ jobs:
- name: Fill test config
run: python .github\workflows\fill_test_config.py
-
+
- name: Export full connection string
shell: bash
run: echo "CONNECTION_STRING=$(< $HOME/CONNECTION_STRING)" >> $GITHUB_ENV
@@ -189,10 +196,10 @@ jobs:
if: startsWith(github.ref, 'refs/tags/')
runs-on: windows-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Install .NET
- uses: actions/setup-dotnet@v4
+ uses: actions/setup-dotnet@v5
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
@@ -206,7 +213,7 @@ jobs:
run: dotnet pack -c Release --output net_connector -p:PackageVersion=${{ env.CONNECTOR_VERSION }}
- name: Upload artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
with:
name: net_connector
path: net_connector/
diff --git a/.github/workflows/fill_test_config.py b/.github/workflows/fill_test_config.py
index 5b11e3272..8a822e7f4 100644
--- a/.github/workflows/fill_test_config.py
+++ b/.github/workflows/fill_test_config.py
@@ -2,7 +2,7 @@
import os
from s2ms_cluster import WORKSPACE_ENDPOINT_FILE
-NET_FRAMEWORKS = ["net462", "net472", "net6.0", "net7.0", "net8.0", "net9.0"]
+NET_FRAMEWORKS = ["net462", "net472", "net6.0", "net7.0", "net8.0", "net10.0"]
if __name__ == "__main__":
diff --git a/Directory.Build.props b/Directory.Build.props
index ff778ea9a..6621f4f4f 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -2,14 +2,16 @@
true
- preview
+ 14.0
true
- $(NoWarn);1591;CA1708;CA1835;CA2215;CA5397;NU5105;SYSLIB0039
+
+ true
+ $(NoWarn);1573;1591;1712;CA1708;CA1835;CA2215;CA5397;NU5105;SYSLIB0039
+
$(MSBuildThisFileDirectory)artifacts
true
low
all
- true
diff --git a/Directory.Packages.props b/Directory.Packages.props
index a01542f93..f579e9895 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -8,28 +8,29 @@
-
+
+
-
-
+
+
-
-
+
-
+
-
+
+
diff --git a/LICENSE b/LICENSE
index a160f10d7..48c547baf 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,7 @@
MIT License
Copyright (c) 2016-2021 Bradley Grainger
-Copyright (c) 2022-2025 SingleStore, Inc.
+Copyright (c) 2022-2026 SingleStore, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 144b8252b..6d15c6cab 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# SingleStore Connector for .NET and .NET Core 1.3.0
+# SingleStore Connector for .NET and .NET Core 1.4.0
This is an [ADO.NET](https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/) data
provider for [SingleStore](https://www.singlestore.com/). It provides implementations of
diff --git a/SingleStoreConnector.slnx b/SingleStoreConnector.slnx
new file mode 100644
index 000000000..7fa8f1875
--- /dev/null
+++ b/SingleStoreConnector.slnx
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SingleStoreNETConnector.sln b/SingleStoreNETConnector.sln
deleted file mode 100644
index 816a48433..000000000
--- a/SingleStoreNETConnector.sln
+++ /dev/null
@@ -1,96 +0,0 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.27130.2010
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SingleStoreConnector", "src\SingleStoreConnector\SingleStoreConnector.csproj", "{F82378AF-274E-4FBA-8E45-27126D607B85}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SideBySide", "tests\SideBySide\SideBySide.csproj", "{407C2AC9-0CCA-4D6B-8698-362976D97730}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Conformance.Tests", "tests\Conformance.Tests\Conformance.Tests.csproj", "{CC0DA702-43E8-471E-9320-F36685C540A1}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SingleStoreConnector.Logging.log4net", "src\SingleStoreConnector.Logging.log4net\SingleStoreConnector.Logging.log4net.csproj", "{A15647B8-FA3F-4536-BF4E-4F93F2FBFC75}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SingleStoreConnector.Logging.Microsoft.Extensions.Logging", "src\SingleStoreConnector.Logging.Microsoft.Extensions.Logging\SingleStoreConnector.Logging.Microsoft.Extensions.Logging.csproj", "{6A3F1732-F874-463E-9BB8-21690E7B8ED0}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SingleStoreConnector.Logging.Serilog", "src\SingleStoreConnector.Logging.Serilog\SingleStoreConnector.Logging.Serilog.csproj", "{38806B85-6526-4A81-9905-45D3411B0AE2}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SingleStoreConnector.Logging.NLog", "src\SingleStoreConnector.Logging.NLog\SingleStoreConnector.Logging.NLog.csproj", "{92015BEE-563A-4595-9243-0510D2B8767F}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SingleStoreConnector.Authentication.Ed25519", "src\SingleStoreConnector.Authentication.Ed25519\SingleStoreConnector.Authentication.Ed25519.csproj", "{5DB4FA2E-910B-47CE-B467-F6852104D567}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SingleStoreConnector.Tests", "tests\SingleStoreConnector.Tests\SingleStoreConnector.Tests.csproj", "{7A89FC1F-4DD2-4D84-A489-57BE246A1FB8}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SchemaCollectionGenerator", "tools\SchemaCollectionGenerator\SchemaCollectionGenerator.csproj", "{1FFFFB30-013B-44DD-B016-7266CDC14A87}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SingleStoreConnector.NativeAot.Tests", "tests\SingleStoreConnector.NativeAot.Tests\SingleStoreConnector.NativeAot.Tests.csproj", "{55C8FCF9-D492-497E-96A5-FEE307481EEA}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SingleStoreConnector.DependencyInjection.Tests", "tests\SingleStoreConnector.DependencyInjection.Tests\SingleStoreConnector.DependencyInjection.Tests.csproj", "{69351125-C517-4FBE-A390-7A91630F170F}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SingleStoreConnector.DependencyInjection", "src\SingleStoreConnector.DependencyInjection\SingleStoreConnector.DependencyInjection.csproj", "{9A904FE0-2558-4088-B56F-B4025A91AEEB}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {F82378AF-274E-4FBA-8E45-27126D607B85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {F82378AF-274E-4FBA-8E45-27126D607B85}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F82378AF-274E-4FBA-8E45-27126D607B85}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {F82378AF-274E-4FBA-8E45-27126D607B85}.Release|Any CPU.Build.0 = Release|Any CPU
- {407C2AC9-0CCA-4D6B-8698-362976D97730}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {407C2AC9-0CCA-4D6B-8698-362976D97730}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {407C2AC9-0CCA-4D6B-8698-362976D97730}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {407C2AC9-0CCA-4D6B-8698-362976D97730}.Release|Any CPU.Build.0 = Release|Any CPU
- {CC0DA702-43E8-471E-9320-F36685C540A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CC0DA702-43E8-471E-9320-F36685C540A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CC0DA702-43E8-471E-9320-F36685C540A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {CC0DA702-43E8-471E-9320-F36685C540A1}.Release|Any CPU.Build.0 = Release|Any CPU
- {A15647B8-FA3F-4536-BF4E-4F93F2FBFC75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A15647B8-FA3F-4536-BF4E-4F93F2FBFC75}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A15647B8-FA3F-4536-BF4E-4F93F2FBFC75}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A15647B8-FA3F-4536-BF4E-4F93F2FBFC75}.Release|Any CPU.Build.0 = Release|Any CPU
- {6A3F1732-F874-463E-9BB8-21690E7B8ED0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {6A3F1732-F874-463E-9BB8-21690E7B8ED0}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {6A3F1732-F874-463E-9BB8-21690E7B8ED0}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {6A3F1732-F874-463E-9BB8-21690E7B8ED0}.Release|Any CPU.Build.0 = Release|Any CPU
- {38806B85-6526-4A81-9905-45D3411B0AE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {38806B85-6526-4A81-9905-45D3411B0AE2}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {38806B85-6526-4A81-9905-45D3411B0AE2}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {38806B85-6526-4A81-9905-45D3411B0AE2}.Release|Any CPU.Build.0 = Release|Any CPU
- {92015BEE-563A-4595-9243-0510D2B8767F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {92015BEE-563A-4595-9243-0510D2B8767F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {92015BEE-563A-4595-9243-0510D2B8767F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {92015BEE-563A-4595-9243-0510D2B8767F}.Release|Any CPU.Build.0 = Release|Any CPU
- {5DB4FA2E-910B-47CE-B467-F6852104D567}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {5DB4FA2E-910B-47CE-B467-F6852104D567}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {5DB4FA2E-910B-47CE-B467-F6852104D567}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {5DB4FA2E-910B-47CE-B467-F6852104D567}.Release|Any CPU.Build.0 = Release|Any CPU
- {7A89FC1F-4DD2-4D84-A489-57BE246A1FB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7A89FC1F-4DD2-4D84-A489-57BE246A1FB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7A89FC1F-4DD2-4D84-A489-57BE246A1FB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7A89FC1F-4DD2-4D84-A489-57BE246A1FB8}.Release|Any CPU.Build.0 = Release|Any CPU
- {1FFFFB30-013B-44DD-B016-7266CDC14A87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {1FFFFB30-013B-44DD-B016-7266CDC14A87}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {1FFFFB30-013B-44DD-B016-7266CDC14A87}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {1FFFFB30-013B-44DD-B016-7266CDC14A87}.Release|Any CPU.Build.0 = Release|Any CPU
- {55C8FCF9-D492-497E-96A5-FEE307481EEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {55C8FCF9-D492-497E-96A5-FEE307481EEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {55C8FCF9-D492-497E-96A5-FEE307481EEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {55C8FCF9-D492-497E-96A5-FEE307481EEA}.Release|Any CPU.Build.0 = Release|Any CPU
- {69351125-C517-4FBE-A390-7A91630F170F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {69351125-C517-4FBE-A390-7A91630F170F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {69351125-C517-4FBE-A390-7A91630F170F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {69351125-C517-4FBE-A390-7A91630F170F}.Release|Any CPU.Build.0 = Release|Any CPU
- {9A904FE0-2558-4088-B56F-B4025A91AEEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {9A904FE0-2558-4088-B56F-B4025A91AEEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {9A904FE0-2558-4088-B56F-B4025A91AEEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {9A904FE0-2558-4088-B56F-B4025A91AEEB}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {4784EA55-5DE0-404F-8BEC-E14C202F01FA}
- EndGlobalSection
-EndGlobal
diff --git a/docs/VersionHistory.md b/docs/VersionHistory.md
index 199100f5c..8392c00a3 100644
--- a/docs/VersionHistory.md
+++ b/docs/VersionHistory.md
@@ -2,6 +2,40 @@
## Release Notes
+### 1.4.0
+
+All the changes introduced with MySqlConnector (2.4.0 - 2.5.0):
+
+* Support .NET 10
+
+* **Possibly breaking** SingleStoreConnection.State will be set to ConnectionState.Broken when there is a network error:
+ - Previously it would have been set to ConnectionState.Closed but the connection wasn't truly closed.
+ - Call SingleStoreConnection.Close() to fully close the connection before calling Open() again.
+ - Better yet, call .Dispose() (ideally with a using declaration) and create a new SingleStoreConnection instance to recover from failure.
+
+* **Possibly breaking** SingleStoreConnection.ResetConnectionAsync will consistently throw a SingleStoreException.
+
+* Support VECTOR and BSON data types
+
+* Add SingleStoreConnectorTracingOptions and builder APIs to configure tracing output
+
+* Mark SingleStoreDbType with [DbProviderSpecificTypeProperty(true)]
+
+* Allow loopback connections (e.g., Google Cloud SQL Proxy) to use clear-text password or public key retrieval
+
+* Improve XA transaction rollback handling
+
+* Free large cached payload buffers when returning sessions to the pool to reduce memory usage
+
+* Suppress error-level logging when a command is canceled
+
+* Fix SingleStoreBulkCopy auto-detected column mappings
+
+* Fix extra roundtrip for caching_sha2_password
+
+* Fix cancellation with AWS RDS Proxy
+
+
### 1.3.0
All the changes introduced with MySqlConnector (2.3.1 - 2.4.0):
diff --git a/docs/content/api/SingleStoreConnector.Authentication/IAuthenticationPlugin3.md b/docs/content/api/SingleStoreConnector.Authentication/IAuthenticationPlugin3.md
new file mode 100644
index 000000000..db5fabd40
--- /dev/null
+++ b/docs/content/api/SingleStoreConnector.Authentication/IAuthenticationPlugin3.md
@@ -0,0 +1,24 @@
+# IAuthenticationPlugin3 interface
+
+[`IAuthenticationPlugin3`](./IAuthenticationPlugin3.md) is an extension to [`IAuthenticationPlugin`](./IAuthenticationPlugin.md) that also returns a hash of the client's password.
+
+```csharp
+public interface IAuthenticationPlugin3 : IAuthenticationPlugin
+```
+
+## Members
+
+| name | description |
+| --- | --- |
+| [CreateResponseAndPasswordHash](IAuthenticationPlugin3/CreateResponseAndPasswordHash.md)(…) | Creates the authentication response and hashes the client's password (e.g., for TLS certificate fingerprint verification). |
+
+## Remarks
+
+If an authentication plugin supports this interface, the base [`CreateResponse`](./IAuthenticationPlugin/CreateResponse.md) method will not be called.
+
+## See Also
+
+* interface [IAuthenticationPlugin](./IAuthenticationPlugin.md)
+* namespace [SingleStoreConnector.Authentication](../SingleStoreConnector.md)
+
+
diff --git a/docs/content/api/SingleStoreConnector.Authentication/IAuthenticationPlugin3/CreateResponseAndPasswordHash.md b/docs/content/api/SingleStoreConnector.Authentication/IAuthenticationPlugin3/CreateResponseAndPasswordHash.md
new file mode 100644
index 000000000..27d2930f7
--- /dev/null
+++ b/docs/content/api/SingleStoreConnector.Authentication/IAuthenticationPlugin3/CreateResponseAndPasswordHash.md
@@ -0,0 +1,22 @@
+# IAuthenticationPlugin3.CreateResponseAndPasswordHash method
+
+Creates the authentication response and hashes the client's password (e.g., for TLS certificate fingerprint verification).
+
+```csharp
+public void CreateResponseAndPasswordHash(string password, ReadOnlySpan authenticationData,
+ out byte[] authenticationResponse, out byte[] passwordHash)
+```
+
+| parameter | description |
+| --- | --- |
+| password | The client's password. |
+| authenticationData | The authentication data supplied by the server; this is the |
+| authenticationResponse | The authentication response. |
+| passwordHash | The authentication-method-specific hash of the client's password. |
+
+## See Also
+
+* interface [IAuthenticationPlugin3](../IAuthenticationPlugin3.md)
+* namespace [SingleStoreConnector.Authentication](../../SingleStoreConnector.md)
+
+
diff --git a/docs/content/api/SingleStoreConnector.Logging/SingleStoreConnectorLogManager/Provider.md b/docs/content/api/SingleStoreConnector.Logging/SingleStoreConnectorLogManager/Provider.md
deleted file mode 100644
index e075156bc..000000000
--- a/docs/content/api/SingleStoreConnector.Logging/SingleStoreConnectorLogManager/Provider.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# SingleStoreConnectorLogManager.Provider property
-
-Allows the [`ISingleStoreConnectorLoggerProvider`](../ISingleStoreConnectorLoggerProvider.md) to be set for this library. `Provider` can be set once, and must be set before any other library methods are used.
-
-```csharp
-public static ISingleStoreConnectorLoggerProvider Provider { set; }
-```
-
-## See Also
-
-* interface [ISingleStoreConnectorLoggerProvider](../ISingleStoreConnectorLoggerProvider.md)
-* class [SingleStoreConnectorLogManager](../SingleStoreConnectorLogManager.md)
-* namespace [SingleStoreConnector.Logging](../../SingleStoreConnector.md)
-
-
diff --git a/docs/content/api/SingleStoreConnector.md b/docs/content/api/SingleStoreConnector.md
index 47e1d8117..183053f5c 100644
--- a/docs/content/api/SingleStoreConnector.md
+++ b/docs/content/api/SingleStoreConnector.md
@@ -26,6 +26,7 @@
| enum [SingleStoreConnectionProtocol](./SingleStoreConnector/SingleStoreConnectionProtocol.md) | Specifies the type of connection to make to the server. |
| class [SingleStoreConnectionStringBuilder](./SingleStoreConnector/SingleStoreConnectionStringBuilder.md) | [`SingleStoreConnectionStringBuilder`](./SingleStoreConnector/SingleStoreConnectionStringBuilder.md) allows you to construct a SingleStore connection string by setting properties on the builder then reading the ConnectionString property. |
| class [SingleStoreConnectorFactory](./SingleStoreConnector/SingleStoreConnectorFactory.md) | An implementation of DbProviderFactory that creates SingleStoreConnector objects. |
+| class [SingleStoreConnectorTracingOptionsBuilder](./SingleStoreConnector/SingleStoreConnectorTracingOptionsBuilder.md) | [`SingleStoreConnectorTracingOptionsBuilder`](./SingleStoreConnector/SingleStoreConnectorTracingOptionsBuilder.md) provides an API for configuring OpenTelemetry tracing options. |
| class [SingleStoreConversionException](./SingleStoreConnector/SingleStoreConversionException.md) | [`SingleStoreConversionException`](./SingleStoreConnector/SingleStoreConversionException.md) is thrown when a SingleStore value can't be converted to another type. |
| class [SingleStoreDataAdapter](./SingleStoreConnector/SingleStoreDataAdapter.md) | |
| class [SingleStoreDataReader](./SingleStoreConnector/SingleStoreDataReader.md) | |
@@ -67,7 +68,7 @@
| --- | --- |
| static class [AuthenticationPlugins](./SingleStoreConnector.Authentication/AuthenticationPlugins.md) | A registry of known authentication plugins. |
| interface [IAuthenticationPlugin](./SingleStoreConnector.Authentication/IAuthenticationPlugin.md) | The primary interface implemented by an authentication plugin. |
-| interface [IAuthenticationPlugin2](./SingleStoreConnector.Authentication/IAuthenticationPlugin2.md) | [`IAuthenticationPlugin2`](./SingleStoreConnector.Authentication/IAuthenticationPlugin2.md) is an extension to [`IAuthenticationPlugin`](./SingleStoreConnector.Authentication/IAuthenticationPlugin.md) that returns a hash of the client's password. |
+| interface [IAuthenticationPlugin3](./SingleStoreConnector.Authentication/IAuthenticationPlugin3.md) | [`IAuthenticationPlugin3`](./SingleStoreConnector.Authentication/IAuthenticationPlugin3.md) is an extension to [`IAuthenticationPlugin`](./SingleStoreConnector.Authentication/IAuthenticationPlugin.md) that also returns a hash of the client's password. |
## SingleStoreConnector.Logging namespace
@@ -81,4 +82,10 @@
| enum [SingleStoreConnectorLogLevel](./SingleStoreConnector.Logging/SingleStoreConnectorLogLevel.md) | |
| static class [SingleStoreConnectorLogManager](./SingleStoreConnector.Logging/SingleStoreConnectorLogManager.md) | Controls logging for SingleStoreConnector. |
+## System.Data.Common namespace
+
+| public type | description |
+| --- | --- |
+| abstract class [DbDataSource](./System.Data.Common/DbDataSource.md) | |
+
diff --git a/docs/content/api/SingleStoreConnector/ConnectionInfo.md b/docs/content/api/SingleStoreConnector/ConnectionInfo.md
new file mode 100644
index 000000000..7f5120480
--- /dev/null
+++ b/docs/content/api/SingleStoreConnector/ConnectionInfo.md
@@ -0,0 +1,24 @@
+# ConnectionInfo structure
+
+```csharp
+public struct ConnectionInfo : IEquatable
+```
+
+## Public Members
+
+| name | description |
+| --- | --- |
+| [ConnectionInfo](ConnectionInfo/ConnectionInfo.md)(…) | |
+| [AggregatorId](ConnectionInfo/AggregatorId.md) { get; } | |
+| [ConnectionId](ConnectionInfo/ConnectionId.md) { get; } | |
+| [Equals](ConnectionInfo/Equals.md)(…) | |
+| override [Equals](ConnectionInfo/Equals.md)(…) | |
+| override [GetHashCode](ConnectionInfo/GetHashCode.md)() | |
+| [operator ==](ConnectionInfo/op_Equality.md) | |
+| [operator !=](ConnectionInfo/op_Inequality.md) | |
+
+## See Also
+
+* namespace [SingleStoreConnector](../SingleStoreConnector.md)
+
+
diff --git a/docs/content/api/SingleStoreConnector/SingleStoreConnection/CreateDbBatch.md b/docs/content/api/SingleStoreConnector/ConnectionInfo/AggregatorId.md
similarity index 53%
rename from docs/content/api/SingleStoreConnector/SingleStoreConnection/CreateDbBatch.md
rename to docs/content/api/SingleStoreConnector/ConnectionInfo/AggregatorId.md
index 27384b075..3c9a00f19 100644
--- a/docs/content/api/SingleStoreConnector/SingleStoreConnection/CreateDbBatch.md
+++ b/docs/content/api/SingleStoreConnector/ConnectionInfo/AggregatorId.md
@@ -1,12 +1,12 @@
-# SingleStoreConnection.CreateDbBatch method
+# ConnectionInfo.AggregatorId property
```csharp
-protected override DbBatch CreateDbBatch()
+public int AggregatorId { get; }
```
## See Also
-* class [SingleStoreConnection](../SingleStoreConnection.md)
+* struct [ConnectionInfo](../ConnectionInfo.md)
* namespace [SingleStoreConnector](../../SingleStoreConnector.md)
diff --git a/docs/content/api/SingleStoreConnector/SingleStoreBatch/DbConnection.md b/docs/content/api/SingleStoreConnector/ConnectionInfo/ConnectionId.md
similarity index 52%
rename from docs/content/api/SingleStoreConnector/SingleStoreBatch/DbConnection.md
rename to docs/content/api/SingleStoreConnector/ConnectionInfo/ConnectionId.md
index 84fb5b0ab..21bb1bc62 100644
--- a/docs/content/api/SingleStoreConnector/SingleStoreBatch/DbConnection.md
+++ b/docs/content/api/SingleStoreConnector/ConnectionInfo/ConnectionId.md
@@ -1,12 +1,12 @@
-# SingleStoreBatch.DbConnection property
+# ConnectionInfo.ConnectionId property
```csharp
-protected override DbConnection? DbConnection { get; set; }
+public int ConnectionId { get; }
```
## See Also
-* class [SingleStoreBatch](../SingleStoreBatch.md)
+* struct [ConnectionInfo](../ConnectionInfo.md)
* namespace [SingleStoreConnector](../../SingleStoreConnector.md)
diff --git a/docs/content/api/SingleStoreConnector/SingleStoreBatch/DbTransaction.md b/docs/content/api/SingleStoreConnector/ConnectionInfo/ConnectionInfo.md
similarity index 52%
rename from docs/content/api/SingleStoreConnector/SingleStoreBatch/DbTransaction.md
rename to docs/content/api/SingleStoreConnector/ConnectionInfo/ConnectionInfo.md
index 6c493e266..77b883e11 100644
--- a/docs/content/api/SingleStoreConnector/SingleStoreBatch/DbTransaction.md
+++ b/docs/content/api/SingleStoreConnector/ConnectionInfo/ConnectionInfo.md
@@ -1,12 +1,12 @@
-# SingleStoreBatch.DbTransaction property
+# ConnectionInfo constructor
```csharp
-protected override DbTransaction? DbTransaction { get; set; }
+public ConnectionInfo(int connectionId, int aggregatorId)
```
## See Also
-* class [SingleStoreBatch](../SingleStoreBatch.md)
+* struct [ConnectionInfo](../ConnectionInfo.md)
* namespace [SingleStoreConnector](../../SingleStoreConnector.md)
diff --git a/docs/content/api/SingleStoreConnector/ConnectionInfo/Equals.md b/docs/content/api/SingleStoreConnector/ConnectionInfo/Equals.md
new file mode 100644
index 000000000..0f3e56c70
--- /dev/null
+++ b/docs/content/api/SingleStoreConnector/ConnectionInfo/Equals.md
@@ -0,0 +1,25 @@
+# ConnectionInfo.Equals method (1 of 2)
+
+```csharp
+public bool Equals(ConnectionInfo other)
+```
+
+## See Also
+
+* struct [ConnectionInfo](../ConnectionInfo.md)
+* namespace [SingleStoreConnector](../../SingleStoreConnector.md)
+
+---
+
+# ConnectionInfo.Equals method (2 of 2)
+
+```csharp
+public override bool Equals(object obj)
+```
+
+## See Also
+
+* struct [ConnectionInfo](../ConnectionInfo.md)
+* namespace [SingleStoreConnector](../../SingleStoreConnector.md)
+
+
diff --git a/docs/content/api/SingleStoreConnector/SingleStoreBatch/CreateDbBatchCommand.md b/docs/content/api/SingleStoreConnector/ConnectionInfo/GetHashCode.md
similarity index 52%
rename from docs/content/api/SingleStoreConnector/SingleStoreBatch/CreateDbBatchCommand.md
rename to docs/content/api/SingleStoreConnector/ConnectionInfo/GetHashCode.md
index 176bbe1ae..24fe0b975 100644
--- a/docs/content/api/SingleStoreConnector/SingleStoreBatch/CreateDbBatchCommand.md
+++ b/docs/content/api/SingleStoreConnector/ConnectionInfo/GetHashCode.md
@@ -1,12 +1,12 @@
-# SingleStoreBatch.CreateDbBatchCommand method
+# ConnectionInfo.GetHashCode method
```csharp
-protected override DbBatchCommand CreateDbBatchCommand()
+public override int GetHashCode()
```
## See Also
-* class [SingleStoreBatch](../SingleStoreBatch.md)
+* struct [ConnectionInfo](../ConnectionInfo.md)
* namespace [SingleStoreConnector](../../SingleStoreConnector.md)
diff --git a/docs/content/api/SingleStoreConnector/ConnectionInfo/op_Equality.md b/docs/content/api/SingleStoreConnector/ConnectionInfo/op_Equality.md
new file mode 100644
index 000000000..d9df1e177
--- /dev/null
+++ b/docs/content/api/SingleStoreConnector/ConnectionInfo/op_Equality.md
@@ -0,0 +1,12 @@
+# ConnectionInfo Equality operator
+
+```csharp
+public static bool operator ==(ConnectionInfo left, ConnectionInfo right)
+```
+
+## See Also
+
+* struct [ConnectionInfo](../ConnectionInfo.md)
+* namespace [SingleStoreConnector](../../SingleStoreConnector.md)
+
+
diff --git a/docs/content/api/SingleStoreConnector/ConnectionInfo/op_Inequality.md b/docs/content/api/SingleStoreConnector/ConnectionInfo/op_Inequality.md
new file mode 100644
index 000000000..ff4cce195
--- /dev/null
+++ b/docs/content/api/SingleStoreConnector/ConnectionInfo/op_Inequality.md
@@ -0,0 +1,12 @@
+# ConnectionInfo Inequality operator
+
+```csharp
+public static bool operator !=(ConnectionInfo left, ConnectionInfo right)
+```
+
+## See Also
+
+* struct [ConnectionInfo](../ConnectionInfo.md)
+* namespace [SingleStoreConnector](../../SingleStoreConnector.md)
+
+
diff --git a/docs/content/api/SingleStoreConnector/SingleStoreBatch.md b/docs/content/api/SingleStoreConnector/SingleStoreBatch.md
index d3281ed8c..8d28ddfb6 100644
--- a/docs/content/api/SingleStoreConnector/SingleStoreBatch.md
+++ b/docs/content/api/SingleStoreConnector/SingleStoreBatch.md
@@ -7,7 +7,7 @@ When using MariaDB (10.2 or later), the commands will be sent in a single batch,
Example usage:
```csharp
-using var connection = new SingleStoreConnection("...connection string...");
+await using var connection = new SingleStoreConnection("...connection string...");
await connection.OpenAsync();
using var batch = new SingleStoreBatch(connection)
@@ -42,7 +42,7 @@ using var batch = new SingleStoreBatch(connection)
```
```csharp
-public sealed class SingleStoreBatch : DbBatch
+public sealed class SingleStoreBatch : IDisposable
```
## Public Members
@@ -53,29 +53,18 @@ public sealed class SingleStoreBatch : DbBatch
| [SingleStoreBatch](SingleStoreBatch/SingleStoreBatch.md)(…) | Initializes a new [`SingleStoreBatch`](./SingleStoreBatch.md) object, setting the [`Connection`](./SingleStoreBatch/Connection.md) and [`Transaction`](./SingleStoreBatch/Transaction.md) if specified. |
| [BatchCommands](SingleStoreBatch/BatchCommands.md) { get; } | The collection of commands that will be executed in the batch. |
| [Connection](SingleStoreBatch/Connection.md) { get; set; } | |
-| override [Timeout](SingleStoreBatch/Timeout.md) { get; set; } | |
+| [Timeout](SingleStoreBatch/Timeout.md) { get; set; } | |
| [Transaction](SingleStoreBatch/Transaction.md) { get; set; } | |
-| override [Cancel](SingleStoreBatch/Cancel.md)() | |
-| override [Dispose](SingleStoreBatch/Dispose.md)() | |
-| override [ExecuteNonQuery](SingleStoreBatch/ExecuteNonQuery.md)() | |
-| override [ExecuteNonQueryAsync](SingleStoreBatch/ExecuteNonQueryAsync.md)(…) | |
+| [Cancel](SingleStoreBatch/Cancel.md)() | |
+| [Dispose](SingleStoreBatch/Dispose.md)() | |
+| [ExecuteNonQuery](SingleStoreBatch/ExecuteNonQuery.md)() | |
+| [ExecuteNonQueryAsync](SingleStoreBatch/ExecuteNonQueryAsync.md)(…) | |
| [ExecuteReader](SingleStoreBatch/ExecuteReader.md)(…) | Executes all the commands in the batch, returning a [`SingleStoreDataReader`](./SingleStoreDataReader.md) that can iterate over the result sets. If multiple resultsets are returned, use [`NextResult`](./SingleStoreDataReader/NextResult.md) to access them. |
| [ExecuteReaderAsync](SingleStoreBatch/ExecuteReaderAsync.md)(…) | Executes all the commands in the batch, returning a [`SingleStoreDataReader`](./SingleStoreDataReader.md) that can iterate over the result sets. If multiple resultsets are returned, use [`NextResultAsync`](./SingleStoreDataReader/NextResultAsync.md) to access them. |
-| override [ExecuteScalar](SingleStoreBatch/ExecuteScalar.md)() | |
-| override [ExecuteScalarAsync](SingleStoreBatch/ExecuteScalarAsync.md)(…) | |
-| override [Prepare](SingleStoreBatch/Prepare.md)() | |
-| override [PrepareAsync](SingleStoreBatch/PrepareAsync.md)(…) | |
-
-## Protected Members
-
-| name | description |
-| --- | --- |
-| override [DbBatchCommands](SingleStoreBatch/DbBatchCommands.md) { get; } | |
-| override [DbConnection](SingleStoreBatch/DbConnection.md) { get; set; } | |
-| override [DbTransaction](SingleStoreBatch/DbTransaction.md) { get; set; } | |
-| override [CreateDbBatchCommand](SingleStoreBatch/CreateDbBatchCommand.md)() | |
-| override [ExecuteDbDataReader](SingleStoreBatch/ExecuteDbDataReader.md)(…) | |
-| override [ExecuteDbDataReaderAsync](SingleStoreBatch/ExecuteDbDataReaderAsync.md)(…) | |
+| [ExecuteScalar](SingleStoreBatch/ExecuteScalar.md)() | |
+| [ExecuteScalarAsync](SingleStoreBatch/ExecuteScalarAsync.md)(…) | |
+| [Prepare](SingleStoreBatch/Prepare.md)() | |
+| [PrepareAsync](SingleStoreBatch/PrepareAsync.md)(…) | |
## Remarks
diff --git a/docs/content/api/SingleStoreConnector/SingleStoreBatch/Cancel.md b/docs/content/api/SingleStoreConnector/SingleStoreBatch/Cancel.md
index ea80f6b02..be1cd903d 100644
--- a/docs/content/api/SingleStoreConnector/SingleStoreBatch/Cancel.md
+++ b/docs/content/api/SingleStoreConnector/SingleStoreBatch/Cancel.md
@@ -1,7 +1,7 @@
# SingleStoreBatch.Cancel method
```csharp
-public override void Cancel()
+public void Cancel()
```
## See Also
diff --git a/docs/content/api/SingleStoreConnector/SingleStoreBatch/DbBatchCommands.md b/docs/content/api/SingleStoreConnector/SingleStoreBatch/DbBatchCommands.md
deleted file mode 100644
index 8f01f67d5..000000000
--- a/docs/content/api/SingleStoreConnector/SingleStoreBatch/DbBatchCommands.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# SingleStoreBatch.DbBatchCommands property
-
-```csharp
-protected override DbBatchCommandCollection DbBatchCommands { get; }
-```
-
-## See Also
-
-* class [SingleStoreBatch](../SingleStoreBatch.md)
-* namespace [SingleStoreConnector](../../SingleStoreConnector.md)
-
-
diff --git a/docs/content/api/SingleStoreConnector/SingleStoreBatch/Dispose.md b/docs/content/api/SingleStoreConnector/SingleStoreBatch/Dispose.md
index f9af1ad15..0abee4513 100644
--- a/docs/content/api/SingleStoreConnector/SingleStoreBatch/Dispose.md
+++ b/docs/content/api/SingleStoreConnector/SingleStoreBatch/Dispose.md
@@ -1,7 +1,7 @@
# SingleStoreBatch.Dispose method
```csharp
-public override void Dispose()
+public void Dispose()
```
## See Also
diff --git a/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteDbDataReader.md b/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteDbDataReader.md
deleted file mode 100644
index d127dc009..000000000
--- a/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteDbDataReader.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# SingleStoreBatch.ExecuteDbDataReader method
-
-```csharp
-protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
-```
-
-## See Also
-
-* class [SingleStoreBatch](../SingleStoreBatch.md)
-* namespace [SingleStoreConnector](../../SingleStoreConnector.md)
-
-
diff --git a/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteDbDataReaderAsync.md b/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteDbDataReaderAsync.md
deleted file mode 100644
index 71195627e..000000000
--- a/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteDbDataReaderAsync.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# SingleStoreBatch.ExecuteDbDataReaderAsync method
-
-```csharp
-protected override Task ExecuteDbDataReaderAsync(CommandBehavior behavior,
- CancellationToken cancellationToken)
-```
-
-## See Also
-
-* class [SingleStoreBatch](../SingleStoreBatch.md)
-* namespace [SingleStoreConnector](../../SingleStoreConnector.md)
-
-
diff --git a/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteNonQuery.md b/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteNonQuery.md
index 78f8f9f68..0ecf00967 100644
--- a/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteNonQuery.md
+++ b/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteNonQuery.md
@@ -1,7 +1,7 @@
# SingleStoreBatch.ExecuteNonQuery method
```csharp
-public override int ExecuteNonQuery()
+public int ExecuteNonQuery()
```
## See Also
diff --git a/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteNonQueryAsync.md b/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteNonQueryAsync.md
index effd8b38a..2fa894e67 100644
--- a/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteNonQueryAsync.md
+++ b/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteNonQueryAsync.md
@@ -1,7 +1,7 @@
# SingleStoreBatch.ExecuteNonQueryAsync method
```csharp
-public override Task ExecuteNonQueryAsync(CancellationToken cancellationToken = default)
+public Task ExecuteNonQueryAsync(CancellationToken cancellationToken = default)
```
## See Also
diff --git a/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteScalar.md b/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteScalar.md
index ea6994205..8794f9d9c 100644
--- a/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteScalar.md
+++ b/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteScalar.md
@@ -1,7 +1,7 @@
# SingleStoreBatch.ExecuteScalar method
```csharp
-public override object? ExecuteScalar()
+public object? ExecuteScalar()
```
## See Also
diff --git a/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteScalarAsync.md b/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteScalarAsync.md
index b5d834d60..c01a88b86 100644
--- a/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteScalarAsync.md
+++ b/docs/content/api/SingleStoreConnector/SingleStoreBatch/ExecuteScalarAsync.md
@@ -1,7 +1,7 @@
# SingleStoreBatch.ExecuteScalarAsync method
```csharp
-public override Task
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
diff --git a/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/CryptoBytes.cs b/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/CryptoBytes.cs
new file mode 100644
index 000000000..1cdef9558
--- /dev/null
+++ b/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/CryptoBytes.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Chaos.NaCl
+{
+ internal static class CryptoBytes
+ {
+ public static void Wipe(byte[] data)
+ {
+ if (data == null)
+ throw new ArgumentNullException("data");
+ InternalWipe(data, 0, data.Length);
+ }
+
+ // Secure wiping is hard
+ // * the GC can move around and copy memory
+ // Perhaps this can be avoided by using unmanaged memory or by fixing the position of the array in memory
+ // * Swap files and error dumps can contain secret information
+ // It seems possible to lock memory in RAM, no idea about error dumps
+ // * Compiler could optimize out the wiping if it knows that data won't be read back
+ // I hope this is enough, suppressing inlining
+ // but perhaps `RtlSecureZeroMemory` is needed
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ internal static void InternalWipe(byte[] data, int offset, int count)
+ {
+ Array.Clear(data, offset, count);
+ }
+
+ // shallow wipe of structs
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ internal static void InternalWipe(ref T data)
+ where T : struct
+ {
+ data = default(T);
+ }
+ }
+}
diff --git a/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Ed25519.cs b/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Ed25519.cs
new file mode 100644
index 000000000..f3a0b7011
--- /dev/null
+++ b/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Ed25519.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Security.Cryptography;
+using Chaos.NaCl.Internal.Ed25519Ref10;
+
+namespace Chaos.NaCl
+{
+ internal static class Ed25519
+ {
+ public static readonly int PublicKeySizeInBytes = 32;
+ public static readonly int SignatureSizeInBytes = 64;
+ public static readonly int ExpandedPrivateKeySizeInBytes = 32 * 2;
+ public static readonly int PrivateKeySeedSizeInBytes = 32;
+ public static readonly int SharedKeySizeInBytes = 32;
+
+ public static void Sign(ArraySegment signature, ArraySegment message, ArraySegment expandedPrivateKey)
+ {
+ if (signature.Array == null)
+ throw new ArgumentNullException("signature.Array");
+ if (signature.Count != SignatureSizeInBytes)
+ throw new ArgumentException("signature.Count");
+ if (expandedPrivateKey.Array == null)
+ throw new ArgumentNullException("expandedPrivateKey.Array");
+ if (expandedPrivateKey.Count != ExpandedPrivateKeySizeInBytes)
+ throw new ArgumentException("expandedPrivateKey.Count");
+ if (message.Array == null)
+ throw new ArgumentNullException("message.Array");
+ Ed25519Operations.crypto_sign2(signature.Array, signature.Offset, message.Array, message.Offset, message.Count, expandedPrivateKey.Array, expandedPrivateKey.Offset);
+ }
+
+ public static byte[] Sign(byte[] message, byte[] expandedPrivateKey)
+ {
+ var signature = new byte[SignatureSizeInBytes];
+ Sign(new ArraySegment(signature), new ArraySegment(message), new ArraySegment(expandedPrivateKey));
+ return signature;
+ }
+
+ public static void KeyPairFromSeed(out byte[] publicKey, out byte[] expandedPrivateKey, byte[] privateKeySeed)
+ {
+ if (privateKeySeed == null)
+ throw new ArgumentNullException("privateKeySeed");
+ if (privateKeySeed.Length != PrivateKeySeedSizeInBytes)
+ throw new ArgumentException("privateKeySeed");
+ var pk = new byte[PublicKeySizeInBytes];
+ var sk = new byte[ExpandedPrivateKeySizeInBytes];
+ Ed25519Operations.crypto_sign_keypair(pk, 0, sk, 0, privateKeySeed, 0);
+ publicKey = pk;
+ expandedPrivateKey = sk;
+ }
+ }
+}
diff --git a/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Internal/Ed25519Ref10/GroupElement.cs b/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Internal/Ed25519Ref10/GroupElement.cs
index 9ae034b6e..692a3cc8b 100644
--- a/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Internal/Ed25519Ref10/GroupElement.cs
+++ b/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Internal/Ed25519Ref10/GroupElement.cs
@@ -1,52 +1,63 @@
-namespace Chaos.NaCl.Internal.Ed25519Ref10;
+using System;
-/*
-ge means group element.
-
-Here the group is the set of pairs (x,y) of field elements (see fe.h)
-satisfying -x^2 + y^2 = 1 + d x^2y^2
-where d = -121665/121666.
+namespace Chaos.NaCl.Internal.Ed25519Ref10
+{
+ /*
+ ge means group element.
-Representations:
- ge_p2 (projective): (X:Y:Z) satisfying x=X/Z, y=Y/Z
- ge_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT
- ge_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T
- ge_precomp (Duif): (y+x,y-x,2dxy)
-*/
+ Here the group is the set of pairs (x,y) of field elements (see fe.h)
+ satisfying -x^2 + y^2 = 1 + d x^2y^2
+ where d = -121665/121666.
-internal struct GroupElementP2
-{
- public FieldElement X;
- public FieldElement Y;
- public FieldElement Z;
-} ;
+ Representations:
+ ge_p2 (projective): (X:Y:Z) satisfying x=X/Z, y=Y/Z
+ ge_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT
+ ge_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T
+ ge_precomp (Duif): (y+x,y-x,2dxy)
+ */
-internal struct GroupElementP3
-{
- public FieldElement X;
- public FieldElement Y;
- public FieldElement Z;
- public FieldElement T;
-} ;
+ internal struct GroupElementP2
+ {
+ public FieldElement X;
+ public FieldElement Y;
+ public FieldElement Z;
+ } ;
-internal struct GroupElementP1P1
-{
- public FieldElement X;
- public FieldElement Y;
- public FieldElement Z;
- public FieldElement T;
-} ;
+ internal struct GroupElementP3
+ {
+ public FieldElement X;
+ public FieldElement Y;
+ public FieldElement Z;
+ public FieldElement T;
+ } ;
-internal struct GroupElementPreComp
-{
- public FieldElement yplusx;
- public FieldElement yminusx;
- public FieldElement xy2d;
+ internal struct GroupElementP1P1
+ {
+ public FieldElement X;
+ public FieldElement Y;
+ public FieldElement Z;
+ public FieldElement T;
+ } ;
- public GroupElementPreComp(FieldElement yplusx, FieldElement yminusx, FieldElement xy2d)
+ internal struct GroupElementPreComp
+ {
+ public FieldElement yplusx;
+ public FieldElement yminusx;
+ public FieldElement xy2d;
+
+ public GroupElementPreComp(FieldElement yplusx, FieldElement yminusx, FieldElement xy2d)
+ {
+ this.yplusx = yplusx;
+ this.yminusx = yminusx;
+ this.xy2d = xy2d;
+ }
+ } ;
+
+ internal struct GroupElementCached
{
- this.yplusx = yplusx;
- this.yminusx = yminusx;
- this.xy2d = xy2d;
- }
-} ;
+ public FieldElement YplusX;
+ public FieldElement YminusX;
+ public FieldElement Z;
+ public FieldElement T2d;
+ } ;
+}
diff --git a/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Internal/Ed25519Ref10/base2.cs b/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Internal/Ed25519Ref10/base2.cs
new file mode 100644
index 000000000..0f5a69a45
--- /dev/null
+++ b/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Internal/Ed25519Ref10/base2.cs
@@ -0,0 +1,50 @@
+using System;
+
+namespace Chaos.NaCl.Internal.Ed25519Ref10
+{
+ internal static partial class LookupTables
+ {
+ internal static readonly GroupElementPreComp[] Base2 = new GroupElementPreComp[]{
+ new GroupElementPreComp(
+ new FieldElement( 25967493,-14356035,29566456,3660896,-12694345,4014787,27544626,-11754271,-6079156,2047605 ),
+ new FieldElement( -12545711,934262,-2722910,3049990,-727428,9406986,12720692,5043384,19500929,-15469378 ),
+ new FieldElement( -8738181,4489570,9688441,-14785194,10184609,-12363380,29287919,11864899,-24514362,-4438546 )
+ ),
+ new GroupElementPreComp(
+ new FieldElement( 15636291,-9688557,24204773,-7912398,616977,-16685262,27787600,-14772189,28944400,-1550024 ),
+ new FieldElement( 16568933,4717097,-11556148,-1102322,15682896,-11807043,16354577,-11775962,7689662,11199574 ),
+ new FieldElement( 30464156,-5976125,-11779434,-15670865,23220365,15915852,7512774,10017326,-17749093,-9920357 )
+ ),
+ new GroupElementPreComp(
+ new FieldElement( 10861363,11473154,27284546,1981175,-30064349,12577861,32867885,14515107,-15438304,10819380 ),
+ new FieldElement( 4708026,6336745,20377586,9066809,-11272109,6594696,-25653668,12483688,-12668491,5581306 ),
+ new FieldElement( 19563160,16186464,-29386857,4097519,10237984,-4348115,28542350,13850243,-23678021,-15815942 )
+ ),
+ new GroupElementPreComp(
+ new FieldElement( 5153746,9909285,1723747,-2777874,30523605,5516873,19480852,5230134,-23952439,-15175766 ),
+ new FieldElement( -30269007,-3463509,7665486,10083793,28475525,1649722,20654025,16520125,30598449,7715701 ),
+ new FieldElement( 28881845,14381568,9657904,3680757,-20181635,7843316,-31400660,1370708,29794553,-1409300 )
+ ),
+ new GroupElementPreComp(
+ new FieldElement( -22518993,-6692182,14201702,-8745502,-23510406,8844726,18474211,-1361450,-13062696,13821877 ),
+ new FieldElement( -6455177,-7839871,3374702,-4740862,-27098617,-10571707,31655028,-7212327,18853322,-14220951 ),
+ new FieldElement( 4566830,-12963868,-28974889,-12240689,-7602672,-2830569,-8514358,-10431137,2207753,-3209784 )
+ ),
+ new GroupElementPreComp(
+ new FieldElement( -25154831,-4185821,29681144,7868801,-6854661,-9423865,-12437364,-663000,-31111463,-16132436 ),
+ new FieldElement( 25576264,-2703214,7349804,-11814844,16472782,9300885,3844789,15725684,171356,6466918 ),
+ new FieldElement( 23103977,13316479,9739013,-16149481,817875,-15038942,8965339,-14088058,-30714912,16193877 )
+ ),
+ new GroupElementPreComp(
+ new FieldElement( -33521811,3180713,-2394130,14003687,-16903474,-16270840,17238398,4729455,-18074513,9256800 ),
+ new FieldElement( -25182317,-4174131,32336398,5036987,-21236817,11360617,22616405,9761698,-19827198,630305 ),
+ new FieldElement( -13720693,2639453,-24237460,-7406481,9494427,-5774029,-6554551,-15960994,-2449256,-14291300 )
+ ),
+ new GroupElementPreComp(
+ new FieldElement( -3151181,-5046075,9282714,6866145,-31907062,-863023,-18940575,15033784,25105118,-7894876 ),
+ new FieldElement( -24326370,15950226,-31801215,-14592823,-11662737,-5090925,1573892,-2625887,2198790,-15804619 ),
+ new FieldElement( -3099351,10324967,-2241613,7453183,-5446979,-2735503,-13812022,-16236442,-32461234,-12290683 )
+ )
+ };
+ }
+}
diff --git a/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Internal/Ed25519Ref10/keypair.cs b/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Internal/Ed25519Ref10/keypair.cs
new file mode 100644
index 000000000..6779f94a4
--- /dev/null
+++ b/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Internal/Ed25519Ref10/keypair.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Security.Cryptography;
+
+namespace Chaos.NaCl.Internal.Ed25519Ref10
+{
+ internal static partial class Ed25519Operations
+ {
+ public static void crypto_sign_keypair(byte[] pk, int pkoffset, byte[] sk, int skoffset, byte[] seed, int seedoffset)
+ {
+ GroupElementP3 A;
+ int i;
+
+ Array.Copy(seed, seedoffset, sk, skoffset, 32);
+#if NET5_0_OR_GREATER
+ byte[] h = SHA512.HashData(sk.AsSpan(skoffset, 32));
+#else
+ using var hash = SHA512.Create();
+ byte[] h = hash.ComputeHash(sk, skoffset, 32);
+#endif
+ ScalarOperations.sc_clamp(h, 0);
+
+ GroupOperations.ge_scalarmult_base(out A, h, 0);
+ GroupOperations.ge_p3_tobytes(pk, pkoffset, ref A);
+
+ for (i = 0; i < 32; ++i) sk[skoffset + 32 + i] = pk[pkoffset + i];
+#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
+ CryptographicOperations.ZeroMemory(h);
+#else
+ CryptoBytes.Wipe(h);
+#endif
+ }
+ }
+}
diff --git a/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Internal/Ed25519Ref10/sign.cs b/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Internal/Ed25519Ref10/sign.cs
new file mode 100644
index 000000000..a56625557
--- /dev/null
+++ b/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Internal/Ed25519Ref10/sign.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Security.Cryptography;
+
+namespace Chaos.NaCl.Internal.Ed25519Ref10
+{
+ internal static partial class Ed25519Operations
+ {
+ public static void crypto_sign2(
+ byte[] sig, int sigoffset,
+ byte[] m, int moffset, int mlen,
+ byte[] sk, int skoffset)
+ {
+ byte[] az;
+ byte[] r;
+ byte[] hram;
+ GroupElementP3 R;
+ using (var hasher = SHA512.Create())
+ {
+ az = hasher.ComputeHash(sk, skoffset, 32);
+ ScalarOperations.sc_clamp(az, 0);
+
+ hasher.Initialize();
+ hasher.TransformBlock(az, 32, 32, null, 0);
+ hasher.TransformFinalBlock(m, moffset, mlen);
+ r = hasher.Hash;
+
+ ScalarOperations.sc_reduce(r);
+ GroupOperations.ge_scalarmult_base(out R, r, 0);
+ GroupOperations.ge_p3_tobytes(sig, sigoffset, ref R);
+
+ hasher.Initialize();
+ hasher.TransformBlock(sig, sigoffset, 32, null, 0);
+ hasher.TransformBlock(sk, skoffset + 32, 32, null, 0);
+ hasher.TransformFinalBlock(m, moffset, mlen);
+ hram = hasher.Hash;
+
+ ScalarOperations.sc_reduce(hram);
+ var s = new byte[32];//todo: remove allocation
+ Array.Copy(sig, sigoffset + 32, s, 0, 32);
+ ScalarOperations.sc_muladd(s, hram, az, r);
+ Array.Copy(s, 0, sig, sigoffset + 32, 32);
+#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
+ CryptographicOperations.ZeroMemory(s);
+#else
+ CryptoBytes.Wipe(s);
+#endif
+ }
+ }
+ }
+}
diff --git a/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Internal/Ed25519Ref10/sqrtm1.cs b/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Internal/Ed25519Ref10/sqrtm1.cs
new file mode 100644
index 000000000..dfa325fe0
--- /dev/null
+++ b/src/SingleStoreConnector.Authentication.Ed25519/Chaos.NaCl/Internal/Ed25519Ref10/sqrtm1.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Chaos.NaCl.Internal.Ed25519Ref10
+{
+ internal static partial class LookupTables
+ {
+ internal static FieldElement sqrtm1 = new FieldElement(-32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482);
+ }
+}
diff --git a/src/SingleStoreConnector.Authentication.Ed25519/CompatibilitySuppressions.xml b/src/SingleStoreConnector.Authentication.Ed25519/CompatibilitySuppressions.xml
new file mode 100644
index 000000000..0aa1c2f25
--- /dev/null
+++ b/src/SingleStoreConnector.Authentication.Ed25519/CompatibilitySuppressions.xml
@@ -0,0 +1,8 @@
+
+
+
+
+ PKV006
+ .NETFramework,Version=v4.5
+
+
diff --git a/src/SingleStoreConnector.Authentication.Ed25519/Ed25519AuthenticationPlugin.cs b/src/SingleStoreConnector.Authentication.Ed25519/Ed25519AuthenticationPlugin.cs
index 79d3ccec6..87a6f11bd 100644
--- a/src/SingleStoreConnector.Authentication.Ed25519/Ed25519AuthenticationPlugin.cs
+++ b/src/SingleStoreConnector.Authentication.Ed25519/Ed25519AuthenticationPlugin.cs
@@ -10,7 +10,9 @@ namespace SingleStoreConnector.Authentication.Ed25519;
/// Provides an implementation of the client_ed25519 authentication plugin for MariaDB.
///
/// See Authentication Plugin - ed25519.
-public sealed class Ed25519AuthenticationPlugin : IAuthenticationPlugin2
+#pragma warning disable CS0618 // Type or member is obsolete
+public sealed class Ed25519AuthenticationPlugin : IAuthenticationPlugin3, IAuthenticationPlugin2
+#pragma warning restore CS0618 // Type or member is obsolete
{
///
/// Registers the Ed25519 authentication plugin with SingleStoreConnector. You must call this method once before
@@ -32,7 +34,7 @@ public static void Install()
///
public byte[] CreateResponse(string password, ReadOnlySpan authenticationData)
{
- CreateResponseAndHash(password, authenticationData, out _, out var authenticationResponse);
+ CreateResponseAndPasswordHash(password, authenticationData, out var authenticationResponse, out _);
return authenticationResponse;
}
@@ -41,11 +43,20 @@ public byte[] CreateResponse(string password, ReadOnlySpan authenticationD
///
public byte[] CreatePasswordHash(string password, ReadOnlySpan authenticationData)
{
- CreateResponseAndHash(password, authenticationData, out var passwordHash, out _);
+ CreateResponseAndPasswordHash(password, authenticationData, out _, out var passwordHash);
return passwordHash;
}
- private static void CreateResponseAndHash(string password, ReadOnlySpan authenticationData, out byte[] passwordHash, out byte[] authenticationResponse)
+ ///
+ /// Creates the authentication response and hashes the client's password (e.g., for TLS certificate fingerprint verification).
+ ///
+ /// The client's password.
+ /// The authentication data supplied by the server; this is the auth method data
+ /// from the Authentication
+ /// Method Switch Request Packet.
+ /// The authentication response.
+ /// The authentication-method-specific hash of the client's password.
+ public void CreateResponseAndPasswordHash(string password, ReadOnlySpan authenticationData, out byte[] authenticationResponse, out byte[] passwordHash)
{
// Java reference: https://github.com/MariaDB/mariadb-connector-j/blob/master/src/main/java/org/mariadb/jdbc/internal/com/send/authentication/Ed25519PasswordPlugin.java
// C reference: https://github.com/MariaDB/server/blob/592fe954ef82be1bc08b29a8e54f7729eb1e1343/plugin/auth_ed25519/ref10/sign.c#L7
@@ -77,8 +88,12 @@ private static void CreateResponseAndHash(string password, ReadOnlySpan au
az[31] |= 64;
*/
+#if NET5_0_OR_GREATER
+ byte[] az = SHA512.HashData(passwordBytes);
+#else
using var sha512 = SHA512.Create();
byte[] az = sha512.ComputeHash(passwordBytes);
+#endif
ScalarOperations.sc_clamp(az, 0);
/*** Java
@@ -104,7 +119,11 @@ private static void CreateResponseAndHash(string password, ReadOnlySpan au
byte[] sm = new byte[64 + authenticationData.Length];
authenticationData.CopyTo(sm.AsSpan().Slice(64));
Buffer.BlockCopy(az, 32, sm, 32, 32);
+#if NET5_0_OR_GREATER
+ byte[] nonce = SHA512.HashData(sm.AsSpan(32, authenticationData.Length + 32));
+#else
byte[] nonce = sha512.ComputeHash(sm, 32, authenticationData.Length + 32);
+#endif
/*** Java
ScalarOps scalar = new ScalarOps();
@@ -162,7 +181,11 @@ private static void CreateResponseAndHash(string password, ReadOnlySpan au
return 0;
*/
+#if NET5_0_OR_GREATER
+ var hram = SHA512.HashData(sm);
+#else
var hram = sha512.ComputeHash(sm);
+#endif
ScalarOperations.sc_reduce(hram);
var temp = new byte[32];
ScalarOperations.sc_muladd(temp, hram, az, nonce);
diff --git a/src/SingleStoreConnector.Authentication.Ed25519/ParsecAuthenticationPlugin.cs b/src/SingleStoreConnector.Authentication.Ed25519/ParsecAuthenticationPlugin.cs
new file mode 100644
index 000000000..ad10061d3
--- /dev/null
+++ b/src/SingleStoreConnector.Authentication.Ed25519/ParsecAuthenticationPlugin.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading;
+
+namespace SingleStoreConnector.Authentication.Ed25519;
+
+///
+/// Provides an implementation of the Parsec authentication plugin for MariaDB.
+///
+public sealed class ParsecAuthenticationPlugin : IAuthenticationPlugin3
+{
+ ///
+ /// Registers the Parsec authentication plugin with SingleStoreConnector. You must call this method once before
+ /// opening a connection that uses Parsec authentication.
+ ///
+ public static void Install()
+ {
+ if (Interlocked.CompareExchange(ref s_isInstalled, 1, 0) == 0)
+ AuthenticationPlugins.Register(new ParsecAuthenticationPlugin());
+ }
+
+ ///
+ /// Gets the authentication plugin name.
+ ///
+ public string Name => "parsec";
+
+ ///
+ /// Creates the authentication response.
+ ///
+ public byte[] CreateResponse(string password, ReadOnlySpan authenticationData)
+ {
+ CreateResponseAndPasswordHash(password, authenticationData, out var response, out _);
+ return response;
+ }
+
+ ///
+ /// Creates the authentication response.
+ ///
+ public void CreateResponseAndPasswordHash(string password, ReadOnlySpan authenticationData, out byte[] authenticationResponse, out byte[] passwordHash)
+ {
+ // first 32 bytes are server scramble
+ var serverScramble = authenticationData.Slice(0, 32);
+
+ // generate client scramble
+#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ Span clientScramble = stackalloc byte[32];
+ RandomNumberGenerator.Fill(clientScramble);
+#else
+ var clientScramble = new byte[32];
+ using var randomNumberGenerator = RandomNumberGenerator.Create();
+ randomNumberGenerator.GetBytes(clientScramble);
+#endif
+
+ // parse extended salt from remaining authentication data and verify format
+ var extendedSalt = authenticationData.Slice(32);
+ if (extendedSalt[0] != (byte) 'P')
+ throw new ArgumentException("Invalid extended salt", nameof(authenticationData));
+ if (extendedSalt[1] is not (>= 0 and <= 3))
+ throw new ArgumentException("Invalid iteration count", nameof(authenticationData));
+
+ var iterationCount = 1024 << extendedSalt[1];
+ var salt = extendedSalt.Slice(2);
+
+ // derive private key using PBKDF2-SHA512
+ byte[] privateKeySeed;
+#if NET6_0_OR_GREATER
+ privateKeySeed = Rfc2898DeriveBytes.Pbkdf2(Encoding.UTF8.GetBytes(password), salt, iterationCount, HashAlgorithmName.SHA512, 32);
+#elif NET472_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ using (var pbkdf2 = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(password), salt.ToArray(), iterationCount, HashAlgorithmName.SHA512))
+ privateKeySeed = pbkdf2.GetBytes(32);
+#else
+ privateKeySeed = Microsoft.AspNetCore.Cryptography.KeyDerivation.KeyDerivation.Pbkdf2(
+ password, salt.ToArray(), Microsoft.AspNetCore.Cryptography.KeyDerivation.KeyDerivationPrf.HMACSHA512,
+ iterationCount, numBytesRequested: 32);
+#endif
+ Chaos.NaCl.Ed25519.KeyPairFromSeed(out var publicKey, out var privateKey, privateKeySeed);
+
+ // generate Ed25519 keypair and sign concatenated scrambles
+ var message = new byte[serverScramble.Length + clientScramble.Length];
+ serverScramble.CopyTo(message);
+ clientScramble.CopyTo(message.AsSpan(serverScramble.Length));
+
+ var signature = Chaos.NaCl.Ed25519.Sign(message, privateKey);
+
+#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
+ CryptographicOperations.ZeroMemory(privateKey);
+#endif
+
+ // return client scramble followed by signature
+ authenticationResponse = new byte[clientScramble.Length + signature.Length];
+ clientScramble.CopyTo(authenticationResponse.AsSpan());
+ signature.CopyTo(authenticationResponse.AsSpan(clientScramble.Length));
+
+ // "password hash" for parsec is the extended salt followed by the public key
+ passwordHash = [(byte) 'P', (byte) iterationCount, .. salt, .. publicKey];
+ }
+
+ private ParsecAuthenticationPlugin()
+ {
+ }
+
+ private static int s_isInstalled;
+}
diff --git a/src/SingleStoreConnector.Authentication.Ed25519/SingleStoreConnector.Authentication.Ed25519.csproj b/src/SingleStoreConnector.Authentication.Ed25519/SingleStoreConnector.Authentication.Ed25519.csproj
index ceb6dae8d..2315aabbe 100644
--- a/src/SingleStoreConnector.Authentication.Ed25519/SingleStoreConnector.Authentication.Ed25519.csproj
+++ b/src/SingleStoreConnector.Authentication.Ed25519/SingleStoreConnector.Authentication.Ed25519.csproj
@@ -1,15 +1,16 @@
- net462;netstandard2.0
+ net462;net472;netstandard2.0;netstandard2.1;net6.0
SingleStoreConnector Ed25519 Authentication Plugin
- Implements the client_ed25519 authentication plugin for SingleStore.
+ Implements the client_ed25519 and parsec authentication plugins for SingleStore.
Copyright 2019–2021 Bradley Grainger
- Copyright 2022-2025 SingleStore Inc.
+ Copyright 2022-2026 SingleStore Inc.
Bradley Grainger
README.md
- singlestore;singlestoreconnector;authentication;ed25519
- SA1001;SA1002;SA1005;SA1011;SA1012;SA1021;SA1025;SA1106;SA1107;SA1111;SA1119;SA1121;SA1300;SA1307;SA1312;SA1401;SA1413;SA1501;SA1505;SA1507;SA1508;SA1512;SA1518;SA1601
+ singlestore;singlestoreconnector;authentication;ed25519;parsec
+
+CA1305;CA1507;CA1802;CA2208;CS0649;IDE0049;SA1001;SA1002;SA1005;SA1011;SA1012;SA1021;SA1025;SA1028;SA1106;SA1107;SA1111;SA1119;SA1121;SA1124;SA1137;SA1214;SA1300;SA1307;SA1309;SA1312;SA1313;SA1401;SA1413;SA1501;SA1505;SA1507;SA1508;SA1509;SA1512;SA1515;SA1518;SA1520;SA1601
SingleStoreConnector.Authentication.Ed25519
@@ -17,8 +18,12 @@
+
+
+
+
-
+
diff --git a/src/SingleStoreConnector.Authentication.Ed25519/docs/README.md b/src/SingleStoreConnector.Authentication.Ed25519/docs/README.md
index 0dd5a9af7..b2d794296 100644
--- a/src/SingleStoreConnector.Authentication.Ed25519/docs/README.md
+++ b/src/SingleStoreConnector.Authentication.Ed25519/docs/README.md
@@ -1,7 +1,13 @@
## About
-This package implements the `client_ed25519` [authentication plugin for MariaDB](https://mariadb.com/kb/en/authentication-plugin-ed25519/).
+This package implements the following authentication plugins for MariaDB:
+
+* [`client_ed25519`](https://mariadb.com/kb/en/authentication-plugin-ed25519/).
+* [PARSEC](https://mariadb.com/kb/en/authentication-plugin-parsec/)
## How to Use
-Call `Ed25519AuthenticationPlugin.Install()` from your application startup code to enable it.
+Call either of the following methods from your application startup code to enable the corresponding authentication plugin:
+
+* `Ed25519AuthenticationPlugin.Install()`
+* `ParsecAuthenticationPlugin.Install()`
diff --git a/src/SingleStoreConnector.DependencyInjection/SingleStoreConnector.DependencyInjection.csproj b/src/SingleStoreConnector.DependencyInjection/SingleStoreConnector.DependencyInjection.csproj
index 74aa1ed7b..85f1372b4 100644
--- a/src/SingleStoreConnector.DependencyInjection/SingleStoreConnector.DependencyInjection.csproj
+++ b/src/SingleStoreConnector.DependencyInjection/SingleStoreConnector.DependencyInjection.csproj
@@ -8,7 +8,7 @@
SingleStoreConnector Dependency Injection Helpers
Provides extension methods for integrating SingleStoreConnector with .NET dependency injection.
Copyright 2022 Bradley Grainger
- Copyright 2022-2025 SingleStore Inc.
+ Copyright 2022-2026 SingleStore Inc.
Bradley Grainger
README.md
singlestoreconnector;dependency injection;iservicecollection
diff --git a/src/SingleStoreConnector.DependencyInjection/docs/README.md b/src/SingleStoreConnector.DependencyInjection/docs/README.md
index 18e04b073..8dbac802a 100644
--- a/src/SingleStoreConnector.DependencyInjection/docs/README.md
+++ b/src/SingleStoreConnector.DependencyInjection/docs/README.md
@@ -51,7 +51,7 @@ builder.Services.AddSingleStoreDataSource("Server=server;User ID=test;Password=t
## Keyed Services
-Use the `AddKeyedSingleStoreDataSource` method to register a `SingleStoreDataSource` as a [keyed service](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-9.0#keyed-services).
+Use the `AddKeyedSingleStoreDataSource` method to register a `SingleStoreDataSource` as a [keyed service](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8/runtime#keyed-di-services).
This is useful if you have multiple connection strings or need to connect to multiple databases.
If the service key is a string, it will automatically be used as the `SingleStoreDataSource` name;
to customize this, call the `AddKeyedSingleStoreDataSource(object?, string, Action)` overload and call `SingleStoreDataSourceBuilder.UseName`.
diff --git a/src/SingleStoreConnector.Logging.Microsoft.Extensions.Logging/SingleStoreConnector.Logging.Microsoft.Extensions.Logging.csproj b/src/SingleStoreConnector.Logging.Microsoft.Extensions.Logging/SingleStoreConnector.Logging.Microsoft.Extensions.Logging.csproj
index aeb9145ac..20f845f68 100644
--- a/src/SingleStoreConnector.Logging.Microsoft.Extensions.Logging/SingleStoreConnector.Logging.Microsoft.Extensions.Logging.csproj
+++ b/src/SingleStoreConnector.Logging.Microsoft.Extensions.Logging/SingleStoreConnector.Logging.Microsoft.Extensions.Logging.csproj
@@ -5,7 +5,7 @@
SingleStoreConnector Logging Adapter for Microsoft.Extensions.Logging
Writes SingleStoreConnector logging output to Microsoft.Extensions.Logging with one line of code.
Copyright 2017–2021 Bradley Grainger
- Copyright 2022-2025 SingleStore Inc.
+ Copyright 2022-2026 SingleStore Inc.
Bradley Grainger
README.md
singlestore;singlestoreconnector;async;ado.net;database;netcore;logging
diff --git a/src/SingleStoreConnector.Logging.NLog/SingleStoreConnector.Logging.NLog.csproj b/src/SingleStoreConnector.Logging.NLog/SingleStoreConnector.Logging.NLog.csproj
index ee53e9584..b067d6378 100644
--- a/src/SingleStoreConnector.Logging.NLog/SingleStoreConnector.Logging.NLog.csproj
+++ b/src/SingleStoreConnector.Logging.NLog/SingleStoreConnector.Logging.NLog.csproj
@@ -5,7 +5,7 @@
SingleStoreConnector Logging Adapter for NLog
Writes lightly-structured SingleStoreConnector logging output to NLog.
Copyright 2018–2021 Bradley Grainger
- Copyright 2022-2025 SingleStore Inc.
+ Copyright 2022-2026 SingleStore Inc.
Rolf Kristensen
README.md
singlestore;singlestoreconnector;async;ado.net;database;netcore;NLog;logging
diff --git a/src/SingleStoreConnector.Logging.Serilog/SingleStoreConnector.Logging.Serilog.csproj b/src/SingleStoreConnector.Logging.Serilog/SingleStoreConnector.Logging.Serilog.csproj
index a4e573015..cfc58a54f 100644
--- a/src/SingleStoreConnector.Logging.Serilog/SingleStoreConnector.Logging.Serilog.csproj
+++ b/src/SingleStoreConnector.Logging.Serilog/SingleStoreConnector.Logging.Serilog.csproj
@@ -5,7 +5,7 @@
SingleStoreConnector Logging Adapter for Serilog
Writes lightly-structured SingleStoreConnector logging output to Serilog.
Copyright 2017–2021 Bradley Grainger
- Copyright 2022-2025 SingleStore Inc.
+ Copyright 2022-2026 SingleStore Inc.
Marc Lewandowski
README.md
singlestore;singlestoreconnector;async;ado.net;database;netcore;serilog;logging
diff --git a/src/SingleStoreConnector.Logging.log4net/SingleStoreConnector.Logging.log4net.csproj b/src/SingleStoreConnector.Logging.log4net/SingleStoreConnector.Logging.log4net.csproj
index 7fc139a96..a5a091dc8 100644
--- a/src/SingleStoreConnector.Logging.log4net/SingleStoreConnector.Logging.log4net.csproj
+++ b/src/SingleStoreConnector.Logging.log4net/SingleStoreConnector.Logging.log4net.csproj
@@ -5,7 +5,7 @@
SingleStoreConnector Logging Adapter for log4net
Writes SingleStoreConnector logging output to log4net with one line of code.
Copyright 2017–2021 Bradley Grainger
- Copyright 2022-2025 SingleStore Inc.
+ Copyright 2022-2026 SingleStore Inc.
Bradley Grainger
README.md
singlestore;singlestoreconnector;async;ado.net;database;netcore;log4net;logging
diff --git a/src/SingleStoreConnector/Authentication/AuthenticationPlugins.cs b/src/SingleStoreConnector/Authentication/AuthenticationPlugins.cs
index 7386deb40..4b2e30697 100644
--- a/src/SingleStoreConnector/Authentication/AuthenticationPlugins.cs
+++ b/src/SingleStoreConnector/Authentication/AuthenticationPlugins.cs
@@ -13,18 +13,8 @@ public static class AuthenticationPlugins
/// The authentication plugin.
public static void Register(IAuthenticationPlugin plugin)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(plugin);
-#else
- if (plugin is null)
- throw new ArgumentNullException(nameof(plugin));
-#endif
-#if NET8_0_OR_GREATER
ArgumentException.ThrowIfNullOrEmpty(plugin.Name);
-#else
- if (string.IsNullOrEmpty(plugin.Name))
- throw new ArgumentException("Invalid plugin name.", nameof(plugin));
-#endif
lock (s_lock)
s_plugins.Add(plugin.Name, plugin);
}
diff --git a/src/SingleStoreConnector/Authentication/IAuthenticationPlugin.cs b/src/SingleStoreConnector/Authentication/IAuthenticationPlugin.cs
index 2be902d31..de3eb9114 100644
--- a/src/SingleStoreConnector/Authentication/IAuthenticationPlugin.cs
+++ b/src/SingleStoreConnector/Authentication/IAuthenticationPlugin.cs
@@ -21,10 +21,10 @@ public interface IAuthenticationPlugin
byte[] CreateResponse(string password, ReadOnlySpan authenticationData);
}
-
///
/// is an extension to that returns a hash of the client's password.
///
+[Obsolete("Use IAuthenticationPlugin3 instead.")]
public interface IAuthenticationPlugin2 : IAuthenticationPlugin
{
///
@@ -37,3 +37,21 @@ public interface IAuthenticationPlugin2 : IAuthenticationPlugin
/// The authentication-method-specific hash of the client's password.
byte[] CreatePasswordHash(string password, ReadOnlySpan authenticationData);
}
+
+///
+/// is an extension to that also returns a hash of the client's password.
+///
+/// If an authentication plugin supports this interface, the base method will not be called.
+public interface IAuthenticationPlugin3 : IAuthenticationPlugin
+{
+ ///
+ /// Creates the authentication response and hashes the client's password (e.g., for TLS certificate fingerprint verification).
+ ///
+ /// The client's password.
+ /// The authentication data supplied by the server; this is the auth method data
+ /// from the Authentication
+ /// Method Switch Request Packet.
+ /// The authentication response.
+ /// The authentication-method-specific hash of the client's password.
+ void CreateResponseAndPasswordHash(string password, ReadOnlySpan authenticationData, out byte[] authenticationResponse, out byte[] passwordHash);
+}
diff --git a/src/SingleStoreConnector/ColumnReaders/TextDateTimeColumnReader.cs b/src/SingleStoreConnector/ColumnReaders/TextDateTimeColumnReader.cs
index 39b748ea1..92345d284 100644
--- a/src/SingleStoreConnector/ColumnReaders/TextDateTimeColumnReader.cs
+++ b/src/SingleStoreConnector/ColumnReaders/TextDateTimeColumnReader.cs
@@ -81,7 +81,7 @@ public static object ParseDateTime(ReadOnlySpan data, bool convertZeroDate
try
{
- return allowZeroDateTime ? (ticks % 10 == 0 ? (object) new SingleStoreDateTime(year, month, day, hour, minute, second, ticks / 10) : throw new NotSupportedException("MySqlDateTime does not support sub-microsecond precision")) :
+ return allowZeroDateTime ? (ticks % 10 == 0 ? (object) new SingleStoreDateTime(year, month, day, hour, minute, second, ticks / 10) : throw new NotSupportedException("SingleStoreDateTime does not support sub-microsecond precision")) :
new DateTime(year, month, day, hour, minute, second, dateTimeKind).AddTicks(ticks);
}
catch (Exception ex)
diff --git a/src/SingleStoreConnector/Core/CachedParameter.cs b/src/SingleStoreConnector/Core/CachedParameter.cs
index afb2f8b98..adbbc55d9 100644
--- a/src/SingleStoreConnector/Core/CachedParameter.cs
+++ b/src/SingleStoreConnector/Core/CachedParameter.cs
@@ -2,7 +2,7 @@ namespace SingleStoreConnector.Core;
internal sealed class CachedParameter
{
- public CachedParameter(int ordinalPosition, string? mode, string name, string dataType, bool unsigned, int length)
+ public CachedParameter(int ordinalPosition, string? mode, string name, string dataType, bool unsigned, int length, SingleStoreGuidFormat guidFormat)
{
Position = ordinalPosition;
if (Position == 0)
@@ -14,7 +14,7 @@ public CachedParameter(int ordinalPosition, string? mode, string name, string da
else if (string.Equals(mode, "out", StringComparison.OrdinalIgnoreCase))
Direction = ParameterDirection.Output;
Name = name;
- SingleStoreDbType = TypeMapper.Instance.GetSingleStoreDbType(dataType, unsigned, length);
+ SingleStoreDbType = TypeMapper.Instance.GetSingleStoreDbType(dataType, unsigned, length, guidFormat);
Length = length;
}
diff --git a/src/SingleStoreConnector/Core/CachedProcedure.cs b/src/SingleStoreConnector/Core/CachedProcedure.cs
index 6d761d86e..1314565f2 100644
--- a/src/SingleStoreConnector/Core/CachedProcedure.cs
+++ b/src/SingleStoreConnector/Core/CachedProcedure.cs
@@ -47,7 +47,8 @@ FROM information_schema.parameters
!reader.IsDBNull(2) ? reader.GetString(2) : "",
dataType,
unsigned,
- length
+ length,
+ connection.GuidFormat
));
}
}
@@ -88,6 +89,10 @@ internal SingleStoreParameterCollection AlignParamsWithDb(SingleStoreParameterCo
if (!alignParam.HasSetDbType)
alignParam.SingleStoreDbType = cachedParam.SingleStoreDbType;
+ // for a GUID column, pass along the length so the out parameter can be cast to the right size
+ if (alignParam.SingleStoreDbType == SingleStoreDbType.Guid && cachedParam.Direction is ParameterDirection.Output or ParameterDirection.InputOutput)
+ alignParam.Size = cachedParam.Length;
+
// cached parameters are ordered by ordinal position
alignedParams.Add(alignParam);
}
@@ -95,7 +100,7 @@ internal SingleStoreParameterCollection AlignParamsWithDb(SingleStoreParameterCo
return alignedParams;
}
- internal static List ParseParameters(string parametersSql)
+ internal static List ParseParameters(string parametersSql, SingleStoreGuidFormat guidFormat)
{
// strip comments
parametersSql = s_cStyleComments.Replace(parametersSql, "");
@@ -140,7 +145,7 @@ internal static List ParseParameters(string parametersSql)
var name = parts.Groups[1].Success ? parts.Groups[1].Value.Replace("``", "`") : parts.Groups[2].Value;
var dataType = ParseDataType(parts.Groups[3].Value, out var unsigned, out var length);
- cachedParameters.Add(CreateCachedParameter(i + 1, direction, name, dataType, unsigned, length, originalString));
+ cachedParameters.Add(CreateCachedParameter(i + 1, direction, name, dataType, unsigned, length, guidFormat, originalString));
}
return cachedParameters;
@@ -178,11 +183,11 @@ internal static string ParseDataType(string sql, out bool unsigned, out int leng
return type ?? list[0];
}
- private static CachedParameter CreateCachedParameter(int ordinal, string? direction, string name, string dataType, bool unsigned, int length, string originalSql)
+ private static CachedParameter CreateCachedParameter(int ordinal, string? direction, string name, string dataType, bool unsigned, int length, SingleStoreGuidFormat guidFormat, string originalSql)
{
try
{
- return new CachedParameter(ordinal, direction, name, dataType, unsigned, length);
+ return new CachedParameter(ordinal, direction, name, dataType, unsigned, length, guidFormat);
}
catch (NullReferenceException ex)
{
@@ -215,7 +220,7 @@ private static CachedParameter CreateCachedParameter(int ordinal, string? direct
private static readonly Regex s_singleLineComments = new(@"(^|\s)--.*?$", RegexOptions.Multiline);
private static readonly Regex s_multipleSpaces = new(@"\s+");
private static readonly Regex s_numericTypes = new(@"(DECIMAL|DEC|FIXED|NUMERIC|FLOAT|DOUBLE PRECISION|DOUBLE|REAL)\s*\([0-9]+(,\s*[0-9]+)\)", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
- private static readonly Regex s_enumOrSet = new(@"(ENUM|SET)\s*\([^)]+\)", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
+ private static readonly Regex s_enumOrSet = new(@"(ENUM|SET)\s*\([^)]+\)", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
private static readonly Regex s_parameterName = new(@"^(?:`((?:[\u0001-\u005F\u0061-\uFFFF]+|``)+)`|([A-Za-z0-9$_\u0080-\uFFFF]+)) (.*)$");
private static readonly Regex s_characterSet = new(" (CHARSET|CHARACTER SET) [A-Za-z0-9_]+", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
private static readonly Regex s_collate = new(" (COLLATE) [A-Za-z0-9_]+", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
diff --git a/src/SingleStoreConnector/Core/ColumnTypeMetadata.cs b/src/SingleStoreConnector/Core/ColumnTypeMetadata.cs
index 423caad97..2ebbe3c5c 100644
--- a/src/SingleStoreConnector/Core/ColumnTypeMetadata.cs
+++ b/src/SingleStoreConnector/Core/ColumnTypeMetadata.cs
@@ -1,8 +1,21 @@
+using System.Runtime.CompilerServices;
+
namespace SingleStoreConnector.Core;
-internal sealed class ColumnTypeMetadata(string dataTypeName, DbTypeMapping dbTypeMapping, SingleStoreDbType mySqlDbType, bool isUnsigned = false, bool binary = false, int length = 0, string? simpleDataTypeName = null, string? createFormat = null, long columnSize = 0)
+internal sealed class ColumnTypeMetadata(string dataTypeName, DbTypeMapping dbTypeMapping, SingleStoreDbType mySqlDbType, bool isUnsigned = false, bool binary = false, int length = 0, string? simpleDataTypeName = null, string? createFormat = null, long columnSize = 0, SingleStoreGuidFormat guidFormat = SingleStoreGuidFormat.Default)
{
- public static string CreateLookupKey(string columnTypeName, bool isUnsigned, int length) => $"{columnTypeName}|{(isUnsigned ? "u" : "s")}|{length}";
+ public static string CreateLookupKey(string columnTypeName, bool isUnsigned, int length, SingleStoreGuidFormat guidFormat) =>
+ $"{columnTypeName}|{(isUnsigned ? "u" : "s")}|{length}|{GetGuidFormatLookupKey(guidFormat)}";
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static string GetGuidFormatLookupKey(SingleStoreGuidFormat guidFormat) =>
+ guidFormat switch
+ {
+ SingleStoreGuidFormat.Char36 => "c36",
+ SingleStoreGuidFormat.Char32 => "c32",
+ SingleStoreGuidFormat.Binary16 or SingleStoreGuidFormat.TimeSwapBinary16 or SingleStoreGuidFormat.LittleEndianBinary16 => "b16",
+ _ => "def",
+ };
public string DataTypeName { get; } = dataTypeName;
public string SimpleDataTypeName { get; } = simpleDataTypeName ?? dataTypeName;
@@ -13,6 +26,7 @@ internal sealed class ColumnTypeMetadata(string dataTypeName, DbTypeMapping dbTy
public long ColumnSize { get; } = columnSize;
public bool IsUnsigned { get; } = isUnsigned;
public int Length { get; } = length;
+ public SingleStoreGuidFormat GuidFormat { get; } = guidFormat;
- public string CreateLookupKey() => CreateLookupKey(DataTypeName, IsUnsigned, Length);
+ public string CreateLookupKey() => CreateLookupKey(DataTypeName, IsUnsigned, Length, GuidFormat);
}
diff --git a/src/SingleStoreConnector/Core/ConnectionPool.cs b/src/SingleStoreConnector/Core/ConnectionPool.cs
index db4e0ee13..c3e0d34e3 100644
--- a/src/SingleStoreConnector/Core/ConnectionPool.cs
+++ b/src/SingleStoreConnector/Core/ConnectionPool.cs
@@ -295,11 +295,11 @@ private async Task RecoverLeakedSessionsAsync(IOBehavior ioBehavior)
foreach (var (session, connection) in recoveredSessions)
{
- // bypass SingleStoreConnection.Dispose(Async), because it's a dummy MySqlConnection that's not set up
+ // bypass SingleStoreConnection.Dispose(Async), because it's a dummy SingleStoreConnection that's not set up
// properly, and simply return the session to the pool directly
await session.ReturnToPoolAsync(ioBehavior, null).ConfigureAwait(false);
- // be explicit about keeping the associated MySqlConnection alive until the session has been returned
+ // be explicit about keeping the associated SingleStoreConnection alive until the session has been returned
GC.KeepAlive(connection);
}
}
diff --git a/src/SingleStoreConnector/Core/ILoadBalancer.cs b/src/SingleStoreConnector/Core/ILoadBalancer.cs
index fd97f9566..6a5dd0837 100644
--- a/src/SingleStoreConnector/Core/ILoadBalancer.cs
+++ b/src/SingleStoreConnector/Core/ILoadBalancer.cs
@@ -34,7 +34,7 @@ public IReadOnlyList LoadBalance(IReadOnlyList hosts)
return shuffled;
#else
var shuffled = new List(hosts);
- // from https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
+ //// 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;
@@ -53,7 +53,6 @@ private RandomLoadBalancer()
m_lock = new();
}
-
private readonly Random m_random;
#if NET9_0_OR_GREATER
private readonly Lock m_lock;
diff --git a/src/SingleStoreConnector/Core/ResultSet.cs b/src/SingleStoreConnector/Core/ResultSet.cs
index 419474560..d21f72f13 100644
--- a/src/SingleStoreConnector/Core/ResultSet.cs
+++ b/src/SingleStoreConnector/Core/ResultSet.cs
@@ -167,7 +167,7 @@ public async Task ReadResultSetHeaderAsync(IOBehavior ioBehavior)
ContainsCommandParameters = true;
WarningCount = 0;
State = ResultSetState.ReadResultSetHeader;
- if (DataReader.Activity is { IsAllDataRequested: true })
+ if (DataReader.Activity is { IsAllDataRequested: true } && (Command?.Connection!.SingleStoreDataSource?.TracingOptions.EnableResultSetHeaderEvent ?? SingleStoreConnectorTracingOptions.Default.EnableResultSetHeaderEvent))
DataReader.Activity.AddEvent(new ActivityEvent("read-result-set-header"));
break;
}
@@ -332,12 +332,8 @@ public bool HasRows
public int GetOrdinal(string name)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(name);
-#else
- if (name is null)
- throw new ArgumentNullException(nameof(name));
-#endif
+
if (!HasResultSet)
throw new InvalidOperationException("There is no current result set.");
diff --git a/src/SingleStoreConnector/Core/Row.cs b/src/SingleStoreConnector/Core/Row.cs
index 77c102979..4ea5ae3a9 100644
--- a/src/SingleStoreConnector/Core/Row.cs
+++ b/src/SingleStoreConnector/Core/Row.cs
@@ -77,13 +77,8 @@ public void SetData(ReadOnlyMemory data)
public object GetValue(int ordinal)
{
-#if NET8_0_OR_GREATER
ArgumentOutOfRangeException.ThrowIfNegative(ordinal);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(ordinal, ResultSet.ColumnDefinitions!.Length);
-#else
- if (ordinal < 0 || ordinal >= ResultSet.ColumnDefinitions!.Length)
- throw new ArgumentOutOfRangeException(nameof(ordinal), $"value must be between 0 and {ResultSet.ColumnDefinitions!.Length - 1}");
-#endif
if (m_dataOffsetLengths[ordinal].Offset == -1)
return DBNull.Value;
@@ -235,8 +230,8 @@ public short GetInt16(int ordinal)
public int GetInt32(int ordinal)
{
- if (ordinal < 0 || ordinal >= ResultSet.ColumnDefinitions!.Length)
- throw new ArgumentOutOfRangeException(nameof(ordinal), $"value must be between 0 and {ResultSet.ColumnDefinitions!.Length - 1}");
+ ArgumentOutOfRangeException.ThrowIfNegative(ordinal);
+ ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(ordinal, ResultSet.ColumnDefinitions!.Length);
if (m_dataOffsetLengths[ordinal].Offset == -1)
throw new InvalidCastException("Can't convert NULL to Int32");
@@ -431,12 +426,8 @@ public SingleStoreDecimal GetSingleStoreDecimal(int ordinal)
public int GetValues(object[] values)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(values);
-#else
- if (values is null)
- throw new ArgumentNullException(nameof(values));
-#endif
+
int count = Math.Min(values.Length, ResultSet.ColumnDefinitions!.Length);
for (int i = 0; i < count; i++)
values[i] = GetValue(i);
@@ -469,24 +460,12 @@ private void CheckBinaryColumn(int ordinal)
private static void CheckBufferArguments(long dataOffset, T[] buffer, int bufferOffset, int length)
{
-#if NET8_0_OR_GREATER
ArgumentOutOfRangeException.ThrowIfNegative(dataOffset);
ArgumentOutOfRangeException.ThrowIfGreaterThan(dataOffset, int.MaxValue);
ArgumentOutOfRangeException.ThrowIfNegative(length);
ArgumentOutOfRangeException.ThrowIfNegative(bufferOffset);
ArgumentOutOfRangeException.ThrowIfGreaterThan(bufferOffset, buffer.Length);
-#else
- if (dataOffset < 0)
- throw new ArgumentOutOfRangeException(nameof(dataOffset), dataOffset, nameof(dataOffset) + " must be non-negative");
- if (dataOffset > int.MaxValue)
- throw new ArgumentOutOfRangeException(nameof(dataOffset), dataOffset, nameof(dataOffset) + " must be a 32-bit integer");
- if (length < 0)
- throw new ArgumentOutOfRangeException(nameof(length), length, nameof(length) + " must be non-negative");
- if (bufferOffset < 0)
- throw new ArgumentOutOfRangeException(nameof(bufferOffset), bufferOffset, nameof(bufferOffset) + " must be non-negative");
- if (bufferOffset > buffer.Length)
- throw new ArgumentOutOfRangeException(nameof(bufferOffset), bufferOffset, nameof(bufferOffset) + " must be within the buffer");
-#endif
+
if (checked(bufferOffset + length) > buffer.Length)
throw new ArgumentException(nameof(bufferOffset) + " + " + nameof(length) + " cannot exceed " + nameof(buffer) + "." + nameof(buffer.Length), nameof(length));
}
diff --git a/src/SingleStoreConnector/Core/SchemaProvider.cs b/src/SingleStoreConnector/Core/SchemaProvider.cs
index 2dd9bdc8b..978a3362d 100644
--- a/src/SingleStoreConnector/Core/SchemaProvider.cs
+++ b/src/SingleStoreConnector/Core/SchemaProvider.cs
@@ -387,9 +387,7 @@ private async Task FillDataTableAsync(IOBehavior ioBehavior, DataTable dataTable
{
await FillDataTableAsync(ioBehavior, dataTable, command =>
{
-#pragma warning disable CA2100
command.CommandText = "SELECT " + string.Join(", ", dataTable.Columns.Cast().Select(static x => x!.ColumnName)) + " FROM INFORMATION_SCHEMA." + tableName;
-#pragma warning restore CA2100
if (columns is { Count: > 0 })
{
command.CommandText += " WHERE " + string.Join(" AND ", columns.Select(static x => $@"{x.Key} = @{x.Key}"));
diff --git a/src/SingleStoreConnector/Core/SchemaProvider.g.cs b/src/SingleStoreConnector/Core/SchemaProvider.g.cs
index be1098c55..c3771f09f 100644
--- a/src/SingleStoreConnector/Core/SchemaProvider.g.cs
+++ b/src/SingleStoreConnector/Core/SchemaProvider.g.cs
@@ -9,12 +9,7 @@ internal sealed partial class SchemaProvider
{
public async ValueTask GetSchemaAsync(IOBehavior ioBehavior, string collectionName, string?[]? restrictionValues, CancellationToken cancellationToken)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(collectionName);
-#else
- if (collectionName is null)
- throw new ArgumentNullException(nameof(collectionName));
-#endif
var dataTable = new DataTable();
if (string.Equals(collectionName, "MetaDataCollections", StringComparison.OrdinalIgnoreCase))
diff --git a/src/SingleStoreConnector/Core/ServerSession.cs b/src/SingleStoreConnector/Core/ServerSession.cs
index 79435489a..4c8e224cf 100644
--- a/src/SingleStoreConnector/Core/ServerSession.cs
+++ b/src/SingleStoreConnector/Core/ServerSession.cs
@@ -1,3 +1,4 @@
+using System.Buffers;
using System.Buffers.Text;
using System.ComponentModel;
using System.Diagnostics;
@@ -83,6 +84,11 @@ public ValueTask ReturnToPoolAsync(IOBehavior ioBehavior, SingleStoreConnection?
LastReturnedTimestamp = Stopwatch.GetTimestamp();
if (Pool is null)
return default;
+
+ // if the payload cache is too large, discard it rather than keeping it alive in the pool; see https://github.com/mysql-net/MySqlConnector/issues/1587
+ if (m_payloadCache.Array?.Length > ProtocolUtility.MaxPacketSize + 1)
+ m_payloadCache.ArraySegment = default;
+
MetricsReporter.RecordUseTime(Pool, Utility.GetElapsedSeconds(LastLeasedTimestamp, LastReturnedTimestamp));
LastLeasedTimestamp = 0;
return Pool.ReturnAsync(ioBehavior, this);
@@ -431,13 +437,13 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
ServerCapabilities = initialHandshake.ProtocolCapabilities & ~(ProtocolCapabilities.MariaDbCacheMetadata | ProtocolCapabilities.QueryAttributes);
// if PluginAuth is supported, then use the specified auth plugin; else, fall back to protocol capabilities to determine the auth type to use
- m_currentAuthenticationMethod = (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.PluginAuth) != 0 ? initialHandshake.AuthPluginName! :
+ var currentAuthenticationMethod = (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.PluginAuth) != 0 ? initialHandshake.AuthPluginName! :
(initialHandshake.ProtocolCapabilities & ProtocolCapabilities.SecureConnection) == 0 ? "mysql_old_password" :
"mysql_native_password";
- Log.ServerSentAuthPluginName(m_logger, Id, m_currentAuthenticationMethod);
- if (m_currentAuthenticationMethod is not "mysql_native_password" and not "sha256_password" and not "caching_sha2_password")
+ Log.ServerSentAuthPluginName(m_logger, Id, currentAuthenticationMethod);
+ if (currentAuthenticationMethod is not "mysql_native_password" and not "sha256_password" and not "caching_sha2_password")
{
- Log.UnsupportedAuthenticationMethod(m_logger, Id, m_currentAuthenticationMethod);
+ Log.UnsupportedAuthenticationMethod(m_logger, Id, currentAuthenticationMethod);
throw new NotSupportedException($"Authentication method '{initialHandshake.AuthPluginName}' is not supported.");
}
@@ -484,7 +490,16 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
cs.ConnectionAttributes = CreateConnectionAttributes(cs.ApplicationName, cs.ConnAttrsExtra);
var password = GetPassword(cs, connection);
- using (var handshakeResponsePayload = HandshakeResponse41Payload.Create(initialHandshake, cs, password, m_useCompression, m_characterSet, m_supportsConnectionAttributes ? cs.ConnectionAttributes : null))
+
+ // send a caching_sha2_password response if the server advertised support in the initial handshake
+ var useCachingSha2 = initialHandshake.AuthPluginName == "caching_sha2_password";
+ byte[] authenticationResponse;
+ if (useCachingSha2)
+ authenticationResponse = AuthenticationUtility.CreateScrambleResponse(Utility.TrimZeroByte(initialHandshake.AuthPluginData.AsSpan()), password);
+ else
+ AuthenticationUtility.CreateResponseAndPasswordHash(password, initialHandshake.AuthPluginData, out authenticationResponse, out m_passwordHash);
+
+ using (var handshakeResponsePayload = HandshakeResponse41Payload.Create(initialHandshake, cs, authenticationResponse, m_useCompression, m_characterSet, m_supportsConnectionAttributes ? cs.ConnectionAttributes : null))
await SendReplyAsync(handshakeResponsePayload, ioBehavior, cancellationToken).ConfigureAwait(false);
payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
@@ -493,6 +508,27 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
{
payload = await SwitchAuthenticationAsync(cs, password, payload, ioBehavior, cancellationToken).ConfigureAwait(false);
}
+
+ // check if caching_sha2_password response was sent
+ if (useCachingSha2 && payload.HeaderByte == CachingSha2ServerResponsePayload.Signature)
+ {
+ // process a successful response, or fall back to sending the password if the server doesn't have it
+ var cachingSha2ServerResponsePayload = CachingSha2ServerResponsePayload.Create(payload.Span);
+ if (cachingSha2ServerResponsePayload.Succeeded)
+ {
+ payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
+ }
+ else if (!m_isSecureConnection && password.Length != 0)
+ {
+ var publicKey = await GetRsaPublicKeyAsync(currentAuthenticationMethod, cs, ioBehavior, cancellationToken).ConfigureAwait(false);
+ payload = await SendEncryptedPasswordAsync(AuthPluginData, publicKey, password, ioBehavior, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ payload = await SendClearPasswordAsync(password, ioBehavior, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
var ok = OkPayload.Create(payload.Span, this);
if (m_sslPolicyErrors != SslPolicyErrors.None)
@@ -514,7 +550,7 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
// there is no shared secret that can be used to validate the certificate
Log.CertificateErrorNoPassword(m_logger, Id, m_sslPolicyErrors);
}
- else if (ValidateFingerprint(ok.StatusInfo, initialHandshake.AuthPluginData.AsSpan(0, 20), password))
+ else if (ValidateFingerprint(ok.StatusInfo, initialHandshake.AuthPluginData.AsSpan(0, 20)))
{
Log.CertificateErrorValidThumbprint(m_logger, Id, m_sslPolicyErrors);
ignoreCertificateError = true;
@@ -566,36 +602,20 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
///
/// The validation hash received from the server.
/// The auth plugin data from the initial handshake.
- /// The user's password.
/// true if the validation hash matches the locally-computed value; otherwise, false.
- private bool ValidateFingerprint(byte[]? validationHash, ReadOnlySpan challenge, string password)
+ private bool ValidateFingerprint(byte[]? validationHash, ReadOnlySpan challenge)
{
// expect 0x01 followed by 64 hex characters giving a SHA2 hash
if (validationHash?.Length != 65 || validationHash[0] != 1)
return false;
- byte[]? passwordHashResult = null;
- switch (m_currentAuthenticationMethod)
- {
- case "mysql_native_password":
- passwordHashResult = AuthenticationUtility.HashPassword([], password, onlyHashPassword: true);
- break;
-
- case "client_ed25519":
- AuthenticationPlugins.TryGetPlugin(m_currentAuthenticationMethod, out var ed25519Plugin);
- if (ed25519Plugin is IAuthenticationPlugin2 plugin2)
- passwordHashResult = plugin2.CreatePasswordHash(password, challenge);
- break;
- }
- if (passwordHashResult is null)
+ // the authentication plugin must have provided a password hash (via IAuthenticationPlugin3) that we saved for future use
+ if (m_passwordHash is null)
return false;
- Span combined = stackalloc byte[32 + challenge.Length + passwordHashResult.Length];
- passwordHashResult.CopyTo(combined);
- challenge.CopyTo(combined[passwordHashResult.Length..]);
- m_remoteCertificateSha2Thumbprint!.CopyTo(combined[(passwordHashResult.Length + challenge.Length)..]);
-
+ // hash password hash || scramble || certificate thumbprint
Span hashBytes = stackalloc byte[32];
+ Span combined = [.. m_passwordHash, .. challenge, .. m_remoteCertificateSha2Thumbprint!];
#if NET5_0_OR_GREATER
SHA256.TryHashData(combined, hashBytes, out _);
#else
@@ -608,6 +628,9 @@ private bool ValidateFingerprint(byte[]? validationHash, ReadOnlySpan chal
static bool TryConvertFromHexString(ReadOnlySpan hexChars, Span data)
{
+#if NET10_0_OR_GREATER
+ return Convert.FromHexString(hexChars, data, out _, out _) == OperationStatus.Done;
+#else
ReadOnlySpan hexDigits = "0123456789ABCDEFabcdef"u8;
for (var i = 0; i < hexChars.Length; i += 2)
{
@@ -622,6 +645,7 @@ static bool TryConvertFromHexString(ReadOnlySpan hexChars, Span data
data[i / 2] = (byte) ((high << 4) | low);
}
return true;
+#endif
}
}
@@ -694,22 +718,40 @@ public static async ValueTask ConnectAndRedirectAsync(ILogger con
return session;
}
+ private async Task SendAndVerifyOkAsync(
+ Func sendAsync,
+ IOBehavior ioBehavior,
+ CancellationToken cancellationToken)
+ {
+ await sendAsync().ConfigureAwait(false);
+
+ var response = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
+ OkPayload.Verify(response.Span, this);
+ }
+
public async Task ResetConnectionAsync(IOBehavior ioBehavior, string targetDatabase = "", CancellationToken cancellationToken = default)
{
if (S2ServerVersion.Version.CompareTo(S2Versions.SupportsResetConnection) < 0)
throw new InvalidOperationException("Resetting connection is not supported in SingleStore " + S2ServerVersion.OriginalString);
Log.ResettingConnection(m_logger, Id);
- await SendAsync(ResetConnectionPayload.Instance, ioBehavior, cancellationToken).ConfigureAwait(false);
- var payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
- OkPayload.Verify(payload.Span, this);
+
+ await SendAndVerifyOkAsync(
+ () => SendAsync(ResetConnectionPayload.Instance, ioBehavior, cancellationToken),
+ ioBehavior,
+ cancellationToken).ConfigureAwait(false);
if (targetDatabase.Length > 0)
{
var useDb = $"USE {targetDatabase}";
- await SendAsync(QueryPayload.Create(SupportsQueryAttributes, Encoding.ASCII.GetBytes(useDb)), ioBehavior, cancellationToken).ConfigureAwait(false);
- payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
- OkPayload.Verify(payload.Span, this);
+
+ await SendAndVerifyOkAsync(
+ () => SendAsync(
+ QueryPayload.Create(SupportsQueryAttributes, Encoding.ASCII.GetBytes(useDb)),
+ ioBehavior,
+ cancellationToken),
+ ioBehavior,
+ cancellationToken).ConfigureAwait(false);
}
}
@@ -743,8 +785,8 @@ public async Task TryResetConnectionAsync(ConnectionSettings cs, SingleSto
DatabaseOverride = null;
}
var password = GetPassword(cs, connection);
- var hashedPassword = AuthenticationUtility.CreateAuthenticationResponse(AuthPluginData!, password);
- using (var changeUserPayload = ChangeUserPayload.Create(cs.UserID, hashedPassword, cs.Database, m_characterSet, m_supportsConnectionAttributes ? cs.ConnectionAttributes : null))
+ AuthenticationUtility.CreateResponseAndPasswordHash(password, AuthPluginData, out var nativeResponse, out m_passwordHash);
+ using (var changeUserPayload = ChangeUserPayload.Create(cs.UserID, nativeResponse, cs.Database, m_characterSet, m_supportsConnectionAttributes ? cs.ConnectionAttributes : null))
await SendAsync(changeUserPayload, ioBehavior, cancellationToken).ConfigureAwait(false);
payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
if (payload.HeaderByte == AuthenticationMethodSwitchRequestPayload.Signature)
@@ -792,18 +834,17 @@ private async Task SwitchAuthenticationAsync(ConnectionSettings cs,
// if the server didn't support the hashed password; rehash with the new challenge
var switchRequest = AuthenticationMethodSwitchRequestPayload.Create(payload.Span);
Log.SwitchingToAuthenticationMethod(m_logger, Id, switchRequest.Name);
- m_currentAuthenticationMethod = switchRequest.Name;
switch (switchRequest.Name)
{
case "mysql_native_password":
AuthPluginData = switchRequest.Data;
- var hashedPassword = AuthenticationUtility.CreateAuthenticationResponse(AuthPluginData, password);
- payload = new(hashedPassword);
+ AuthenticationUtility.CreateResponseAndPasswordHash(password, AuthPluginData, out var nativeResponse, out m_passwordHash);
+ payload = new(nativeResponse);
await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);
return await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
case "mysql_clear_password":
- if (!m_isSecureConnection)
+ if (!m_isSecureConnection && !m_isLoopbackConnection)
{
Log.NeedsSecureConnection(m_logger, Id, switchRequest.Name);
throw new SingleStoreException(SingleStoreErrorCode.UnableToConnectToHost, $"Authentication method '{switchRequest.Name}' requires a secure connection.");
@@ -850,9 +891,31 @@ private async Task SwitchAuthenticationAsync(ConnectionSettings cs,
throw new NotSupportedException("'MySQL Server is requesting the insecure pre-4.1 auth mechanism (mysql_old_password). The user password must be upgraded; see https://dev.mysql.com/doc/refman/5.7/en/account-upgrades.html.");
case "client_ed25519":
- if (!AuthenticationPlugins.TryGetPlugin(switchRequest.Name, out var ed25519Plugin))
+ if (!AuthenticationPlugins.TryGetPlugin(switchRequest.Name, out var ed25519Plugin) || ed25519Plugin is not IAuthenticationPlugin3 ed25519Plugin3)
throw new NotSupportedException("You must install the SingleStoreConnector.Authentication.Ed25519 package and call Ed25519AuthenticationPlugin.Install to use client_ed25519 authentication.");
- payload = new(ed25519Plugin.CreateResponse(password, switchRequest.Data));
+ ed25519Plugin3.CreateResponseAndPasswordHash(password, switchRequest.Data, out var ed25519Response, out m_passwordHash);
+ payload = new(ed25519Response);
+ await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);
+ return await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
+
+ case "parsec":
+ if (!AuthenticationPlugins.TryGetPlugin(switchRequest.Name, out var parsecPlugin) || parsecPlugin is not IAuthenticationPlugin3 parsecPlugin3)
+ throw new NotSupportedException("You must install the SingleStoreConnector.Authentication.Ed25519 package and call ParsecAuthenticationPlugin.Install to use parsec authentication.");
+ payload = new([]);
+ await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);
+ payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
+ var extendedSalt = payload.Span;
+
+ // MariaDB 11.8.4 sends an extra 0x01 byte at the beginning of ext-salt: https://github.com/mysql-net/MySqlConnector/issues/1606
+ if (extendedSalt.Length > 2 && extendedSalt[0] == 1 && extendedSalt[1] == 'P')
+ extendedSalt = extendedSalt[1..];
+
+ Span combinedData = stackalloc byte[switchRequest.Data.Length + extendedSalt.Length];
+ switchRequest.Data.CopyTo(combinedData);
+ extendedSalt.CopyTo(combinedData.Slice(switchRequest.Data.Length));
+
+ parsecPlugin3.CreateResponseAndPasswordHash(password, combinedData, out var parsecResponse, out m_passwordHash);
+ payload = new(parsecResponse);
await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);
return await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
@@ -875,7 +938,7 @@ private async Task SendClearPasswordAsync(string password, IOBehavi
private async Task SendEncryptedPasswordAsync(
byte[] switchRequestData,
- string rsaPublicKey,
+ byte[] rsaPublicKey,
string password,
IOBehavior ioBehavior,
CancellationToken cancellationToken)
@@ -896,7 +959,7 @@ private async Task SendEncryptedPasswordAsync(
RSAParameters rsaParameters;
try
{
- rsaParameters = Utility.GetRsaParameters(rsaPublicKey);
+ rsaParameters = Utility.GetRsaParameters(Encoding.ASCII.GetString(rsaPublicKey));
}
catch (Exception ex)
{
@@ -923,13 +986,13 @@ private async Task SendEncryptedPasswordAsync(
return await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
}
- private async Task GetRsaPublicKeyAsync(string switchRequestName, ConnectionSettings cs, IOBehavior ioBehavior, CancellationToken cancellationToken)
+ private async Task GetRsaPublicKeyAsync(string switchRequestName, ConnectionSettings cs, IOBehavior ioBehavior, CancellationToken cancellationToken)
{
if (cs.ServerRsaPublicKeyFile.Length != 0)
{
try
{
- return File.ReadAllText(cs.ServerRsaPublicKeyFile);
+ return File.ReadAllBytes(cs.ServerRsaPublicKeyFile);
}
catch (IOException ex)
{
@@ -938,14 +1001,14 @@ private async Task GetRsaPublicKeyAsync(string switchRequestName, Connec
}
}
- if (cs.AllowPublicKeyRetrieval)
+ if (cs.AllowPublicKeyRetrieval|| m_isLoopbackConnection)
{
// request the RSA public key
var payloadContent = switchRequestName == "caching_sha2_password" ? (byte) 0x02 : (byte) 0x01;
await SendReplyAsync(new PayloadData([payloadContent]), ioBehavior, cancellationToken).ConfigureAwait(false);
var payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
var publicKeyPayload = AuthenticationMoreDataPayload.Create(payload.Span);
- return Encoding.ASCII.GetString(publicKeyPayload.Data);
+ return publicKeyPayload.Data;
}
Log.CouldNotUseAuthenticationMethodForRsa(m_logger, Id, switchRequestName);
@@ -972,7 +1035,7 @@ public async ValueTask TryPingAsync(bool logInfo, IOBehavior ioBehavior, C
}
catch (SingleStoreException ex) when (ex.ErrorCode == SingleStoreErrorCode.ClientInteractionTimeout)
{
- Log.PingFailed(m_logger, ex, Id, "ClientInteractionTimeout MySqlException");
+ Log.PingFailed(m_logger, ex, Id, "ClientInteractionTimeout SingleStoreException");
}
catch (SocketException ex)
{
@@ -1243,6 +1306,7 @@ private async Task OpenTcpSocketAsync(ConnectionSettings cs, ILoadBalancer
m_socket.NoDelay = true;
m_stream = m_tcpClient.GetStream();
m_socket.SetKeepAlive(cs.Keepalive);
+ m_isLoopbackConnection = IPAddress.IsLoopback(ipAddress);
}
catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested)
{
@@ -1671,7 +1735,8 @@ await sslStream.AuthenticateAsClientAsync(clientAuthenticationOptions.TargetHost
}
catch (Exception ex)
{
- Log.CouldNotInitializeTlsConnection(m_logger, ex, Id);
+ if (ex is not OperationCanceledException)
+ Log.CouldNotInitializeTlsConnection(m_logger, ex, Id);
sslStream.Dispose();
ShutdownSocket();
HostName = "";
@@ -1808,6 +1873,13 @@ private bool ShouldGetRealServerDetails(ConnectionSettings cs)
HostName.EndsWith(".mysql.database.chinacloudapi.cn", StringComparison.OrdinalIgnoreCase);
}
+ // detect AWS RDS Proxy, if hostname like .proxy-..rds.amazonaws.com
+ if (HostName.EndsWith(".rds.amazonaws.com", StringComparison.OrdinalIgnoreCase) &&
+ HostName.AsSpan().Contains(".proxy-".AsSpan(), StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
return false;
}
@@ -1940,7 +2012,7 @@ internal void SetFailed(Exception exception)
lock (m_lock)
m_state = State.Failed;
if (OwningConnection is not null && OwningConnection.TryGetTarget(out var connection))
- connection.SetState(ConnectionState.Closed);
+ connection.SetState(ConnectionState.Broken);
}
private void VerifyState(State state)
@@ -2137,7 +2209,11 @@ protected override void OnStatementBegin(int index)
private static readonly PayloadData s_selectConnectionIdVersionWithAttributesPayload = QueryPayload.Create(true, "SELECT CONNECTION_ID(), VERSION(), @@memsql_version, @@aggregator_id;"u8);
private readonly ILogger m_logger;
+#if NET9_0_OR_GREATER
+ private readonly Lock m_lock;
+#else
private readonly object m_lock;
+#endif
private readonly ArraySegmentHolder m_payloadCache;
private readonly ActivityTagsCollection m_activityTags;
private State m_state;
@@ -2149,12 +2225,13 @@ protected override void OnStatementBegin(int index)
private IPayloadHandler? m_payloadHandler;
private bool m_useCompression;
private bool m_isSecureConnection;
+ private bool m_isLoopbackConnection;
private bool m_supportsConnectionAttributes;
private bool m_supportsPipelining;
private CharacterSet m_characterSet;
private PayloadData m_setNamesPayload;
private Dictionary? m_preparedStatements;
- private string? m_currentAuthenticationMethod;
+ private byte[]? m_passwordHash;
private byte[]? m_remoteCertificateSha2Thumbprint;
private SslPolicyErrors m_sslPolicyErrors;
}
diff --git a/src/SingleStoreConnector/Core/SingleCommandPayloadCreator.cs b/src/SingleStoreConnector/Core/SingleCommandPayloadCreator.cs
index 00463f8bc..13c257c8c 100644
--- a/src/SingleStoreConnector/Core/SingleCommandPayloadCreator.cs
+++ b/src/SingleStoreConnector/Core/SingleCommandPayloadCreator.cs
@@ -1,3 +1,6 @@
+using System.Buffers;
+using System.Buffers.Binary;
+using System.Runtime.InteropServices;
using SingleStoreConnector.Logging;
using SingleStoreConnector.Protocol;
using SingleStoreConnector.Protocol.Serialization;
@@ -58,6 +61,7 @@ public bool WriteQueryCommand(ref CommandListPosition commandListPosition, IDict
{
commandListPosition.CommandIndex++;
commandListPosition.PreparedStatementIndex = 0;
+ commandListPosition.PreparedStatements = null;
}
}
return true;
@@ -214,8 +218,24 @@ private static bool WriteStoredProcedure(ISingleStoreCommand command, IDictionar
break;
case ParameterDirection.Output:
outParameters.Add(param);
- outParameterNames.Add(outName);
argParameterNames.Add(outName);
+
+ // special handling for GUIDs to ensure that the result set has a type and length that will be autodetected as a GUID
+ switch (param.SingleStoreDbType, param.Size)
+ {
+ case (SingleStoreDbType.Guid, 16):
+ outParameterNames.Add($"CAST({outName} AS BINARY(16))");
+ break;
+ case (SingleStoreDbType.Guid, 32):
+ outParameterNames.Add($"CAST({outName} AS CHAR(32))");
+ break;
+ case (SingleStoreDbType.Guid, 36):
+ outParameterNames.Add($"CAST({outName} AS CHAR(36))");
+ break;
+ default:
+ outParameterNames.Add(outName);
+ break;
+ }
break;
case ParameterDirection.ReturnValue:
returnParameter = param;
diff --git a/src/SingleStoreConnector/Core/SqlParser.cs b/src/SingleStoreConnector/Core/SqlParser.cs
index ef85cc967..81f34d2ee 100644
--- a/src/SingleStoreConnector/Core/SqlParser.cs
+++ b/src/SingleStoreConnector/Core/SqlParser.cs
@@ -8,12 +8,8 @@ internal abstract class SqlParser(StatementPreparer preparer)
public void Parse(string sql)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(sql);
-#else
- if (sql is null)
- throw new ArgumentNullException(nameof(sql));
-#endif
+
OnBeforeParse(sql);
int parameterStartIndex = -1;
diff --git a/src/SingleStoreConnector/Core/TypeMapper.cs b/src/SingleStoreConnector/Core/TypeMapper.cs
index 59af72101..27abe20d2 100644
--- a/src/SingleStoreConnector/Core/TypeMapper.cs
+++ b/src/SingleStoreConnector/Core/TypeMapper.cs
@@ -110,6 +110,9 @@ private TypeMapper()
#endif
var typeGuid = AddDbTypeMapping(new(typeof(Guid), [DbType.Guid], convert: convertGuid));
AddColumnTypeMetadata(new("CHAR", typeGuid, SingleStoreDbType.Guid, length: 36, simpleDataTypeName: "CHAR(36)", createFormat: "CHAR(36)"));
+ AddColumnTypeMetadata(new("CHAR", typeGuid, SingleStoreDbType.Guid, length: 32, guidFormat: SingleStoreGuidFormat.Char32));
+ AddColumnTypeMetadata(new("CHAR", typeGuid, SingleStoreDbType.Guid, length: 36, guidFormat: SingleStoreGuidFormat.Char36));
+ AddColumnTypeMetadata(new("BINARY", typeGuid, SingleStoreDbType.Guid, binary: true, length: 16, guidFormat: SingleStoreGuidFormat.Binary16));
// null
var typeNull = AddDbTypeMapping(new(typeof(object), [DbType.Object]));
@@ -176,15 +179,20 @@ private void AddColumnTypeMetadata(ColumnTypeMetadata columnTypeMetadata)
public DbTypeMapping? GetDbTypeMapping(string columnTypeName, bool unsigned = false, int length = 0)
{
- return GetColumnTypeMetadata(columnTypeName, unsigned, length)?.DbTypeMapping;
+ return GetColumnTypeMetadata(columnTypeName, unsigned, length, SingleStoreGuidFormat.Default)?.DbTypeMapping;
}
- public SingleStoreDbType GetSingleStoreDbType(string typeName, bool unsigned, int length) => GetColumnTypeMetadata(typeName, unsigned, length)!.SingleStoreDbType;
+ public SingleStoreDbType GetSingleStoreDbType(string typeName, bool unsigned, int length, SingleStoreGuidFormat guidFormat) =>
+ GetColumnTypeMetadata(typeName, unsigned, length, guidFormat)!.SingleStoreDbType;
- private ColumnTypeMetadata? GetColumnTypeMetadata(string columnTypeName, bool unsigned, int length)
+ private ColumnTypeMetadata? GetColumnTypeMetadata(string columnTypeName, bool unsigned, int length, SingleStoreGuidFormat guidFormat)
{
- if (!m_columnTypeMetadataLookup.TryGetValue(ColumnTypeMetadata.CreateLookupKey(columnTypeName, unsigned, length), out var columnTypeMetadata) && length != 0)
- m_columnTypeMetadataLookup.TryGetValue(ColumnTypeMetadata.CreateLookupKey(columnTypeName, unsigned, 0), out columnTypeMetadata);
+ if (m_columnTypeMetadataLookup.TryGetValue(ColumnTypeMetadata.CreateLookupKey(columnTypeName, unsigned, length, guidFormat), out var columnTypeMetadata))
+ return columnTypeMetadata;
+ if (guidFormat != SingleStoreGuidFormat.Default && m_columnTypeMetadataLookup.TryGetValue(ColumnTypeMetadata.CreateLookupKey(columnTypeName, unsigned, length, SingleStoreGuidFormat.Default), out columnTypeMetadata))
+ return columnTypeMetadata;
+ if (length != 0)
+ m_columnTypeMetadataLookup.TryGetValue(ColumnTypeMetadata.CreateLookupKey(columnTypeName, unsigned, 0, SingleStoreGuidFormat.Default), out columnTypeMetadata);
return columnTypeMetadata;
}
diff --git a/src/SingleStoreConnector/Core/XaEnlistedTransaction.cs b/src/SingleStoreConnector/Core/XaEnlistedTransaction.cs
index 015d51796..1ed68429b 100644
--- a/src/SingleStoreConnector/Core/XaEnlistedTransaction.cs
+++ b/src/SingleStoreConnector/Core/XaEnlistedTransaction.cs
@@ -30,16 +30,25 @@ protected override void OnCommit(Enlistment enlistment)
protected override void OnRollback(Enlistment enlistment)
{
- try
+ if (!IsPrepared)
{
- if (!IsPrepared)
+ try
+ {
ExecuteXaCommand("END");
+ }
+ catch (SingleStoreException ex) when (ex.ErrorCode is SingleStoreErrorCode.XARBDeadlock || (ex.ErrorCode is SingleStoreErrorCode.XAERRemoveFail && ex.Message.Contains("ROLLBACK ONLY")))
+ {
+ // ignore deadlock notification AND any unprepared end failure when XAERRemoveFail is returned telling us the XA state is ROLLBACK ONLY.
+ }
+ }
+ try
+ {
ExecuteXaCommand("ROLLBACK");
}
catch (SingleStoreException ex) when (ex.ErrorCode is SingleStoreErrorCode.XARBDeadlock)
{
- // ignore deadlock when rolling back
+ // ignore deadlock notification when rolling back.
}
}
diff --git a/src/SingleStoreConnector/ExceptionExtensions.cs b/src/SingleStoreConnector/ExceptionExtensions.cs
new file mode 100644
index 000000000..b6f94cd92
--- /dev/null
+++ b/src/SingleStoreConnector/ExceptionExtensions.cs
@@ -0,0 +1,98 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace SingleStoreConnector;
+
+internal static class ExceptionExtensions
+{
+#if !NET8_0_OR_GREATER
+ // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/ArgumentException.cs
+ extension(ArgumentException)
+ {
+ /// Throws an exception if is null or empty.
+ /// The string argument to validate as non-null and non-empty.
+ /// The name of the parameter with which corresponds.
+ /// is null.
+ /// is empty.
+ public static void ThrowIfNullOrEmpty([NotNull] string? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
+ {
+ if (argument is null)
+ throw new ArgumentNullException(paramName);
+ if (argument.Length == 0)
+ throw new ArgumentException("The string must not be empty.", paramName);
+ }
+ }
+#endif
+
+#if !NET6_0_OR_GREATER
+ // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/ArgumentNullException.cs
+ extension(ArgumentNullException)
+ {
+ /// Throws an if is null.
+ /// The reference type argument to validate as non-null.
+ /// The name of the parameter with which corresponds.
+ /// From https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/ArgumentNullException.cs.
+ public static void ThrowIfNull([NotNull] object? argument, [CallerArgumentExpression("argument")] string? paramName = null)
+ {
+ if (argument is null)
+ throw new ArgumentNullException(paramName);
+ }
+ }
+#endif
+
+#if !NET8_0_OR_GREATER
+ extension (ArgumentOutOfRangeException)
+ {
+ /// Throws an if is negative.
+ /// The argument to validate as non-negative.
+ /// The name of the parameter with which corresponds.
+ public static void ThrowIfNegative(int value, [CallerArgumentExpression("value")] string? paramName = null)
+ {
+ if (value < 0)
+ throw new ArgumentOutOfRangeException(paramName, "The value must not be negative.");
+ }
+
+ /// Throws an if is negative.
+ /// The argument to validate as non-negative.
+ /// The name of the parameter with which corresponds.
+ public static void ThrowIfNegative(long value, [CallerArgumentExpression("value")] string? paramName = null)
+ {
+ if (value < 0L)
+ throw new ArgumentOutOfRangeException(paramName, "The value must not be negative.");
+ }
+
+ /// Throws an if is greater than .
+ /// The argument to validate as less or equal than .
+ /// The value to compare with .
+ /// The name of the parameter with which corresponds.
+ public static void ThrowIfGreaterThan(T value, T other, [CallerArgumentExpression(nameof(value))] string? paramName = null)
+ where T : IComparable
+ {
+ if (value.CompareTo(other) > 0)
+ throw new ArgumentOutOfRangeException(paramName, $"The value must be less than or equal to {other}.");
+ }
+
+ /// Throws an if is greater than or equal .
+ /// The argument to validate as less than .
+ /// The value to compare with .
+ /// The name of the parameter with which corresponds.
+ public static void ThrowIfGreaterThanOrEqual(T value, T other, [CallerArgumentExpression(nameof(value))] string? paramName = null)
+ where T : IComparable
+ {
+ if (value.CompareTo(other) >= 0)
+ throw new ArgumentOutOfRangeException(paramName, $"The value must be less than {other}.");
+ }
+
+ /// Throws an if is less than or equal .
+ /// The argument to validate as greater than than .
+ /// The value to compare with .
+ /// The name of the parameter with which corresponds.
+ public static void ThrowIfLessThanOrEqual(T value, T other, [CallerArgumentExpression(nameof(value))] string? paramName = null)
+ where T : IComparable
+ {
+ if (value.CompareTo(other) <= 0)
+ throw new ArgumentOutOfRangeException(paramName, $"The value must be greater than {other}.");
+ }
+ }
+#endif
+}
diff --git a/src/SingleStoreConnector/Logging/EventIds.cs b/src/SingleStoreConnector/Logging/EventIds.cs
index 414b8f517..29fb2a86c 100644
--- a/src/SingleStoreConnector/Logging/EventIds.cs
+++ b/src/SingleStoreConnector/Logging/EventIds.cs
@@ -25,6 +25,8 @@ internal static class EventIds
public const int ExpectedToReadMoreBytes = 2010;
public const int ExpectedSessionState1 = 2011;
public const int ExpectedSessionState6 = 2016;
+ public const int ResettingConnectionFailed = 2017;
+ public const int ResetConnection = 2018;
// Session connecting events, 2100-2199
public const int ConnectingFailed = 2100;
diff --git a/src/SingleStoreConnector/Logging/Log.cs b/src/SingleStoreConnector/Logging/Log.cs
index 8fbf8b367..bd0e66124 100644
--- a/src/SingleStoreConnector/Logging/Log.cs
+++ b/src/SingleStoreConnector/Logging/Log.cs
@@ -28,6 +28,12 @@ internal static partial class Log
[LoggerMessage(EventIds.ResettingConnection, LogLevel.Debug, "Session {SessionId} resetting connection")]
public static partial void ResettingConnection(ILogger logger, string sessionId);
+ [LoggerMessage(EventIds.ResettingConnectionFailed, LogLevel.Warning, "Session {SessionId} failed to reset connection: {Message}")]
+ public static partial void ResettingConnectionFailed(ILogger logger, string sessionId, string message);
+
+ [LoggerMessage(EventIds.ResetConnection, LogLevel.Debug, "Session {SessionId} reset connection")]
+ public static partial void ResetConnection(ILogger logger, string sessionId);
+
[LoggerMessage(EventIds.ReturningToPool, LogLevel.Trace, "Session {SessionId} returning to pool {PoolId}")]
public static partial void ReturningToPool(ILogger logger, string sessionId, int poolId);
@@ -166,10 +172,10 @@ internal static partial class Log
#if NETCOREAPP3_0_OR_GREATER
[LoggerMessage(EventIds.ConnectedTlsBasic, LogLevel.Debug, "Session {SessionId} connected TLS using {SslProtocol} and {NegotiatedCipherSuite}")]
public static partial void ConnectedTlsBasic(ILogger logger, string sessionId, SslProtocols sslProtocol, TlsCipherSuite negotiatedCipherSuite);
-#endif
-
+#else
[LoggerMessage(EventIds.ConnectedTlsDetailed, LogLevel.Debug, "Session {SessionId} connected TLS using {SslProtocol}, {CipherAlgorithm}, {HashAlgorithm}, {KeyExchangeAlgorithm}, {KeyExchangeStrength}")]
public static partial void ConnectedTlsDetailed(ILogger logger, string sessionId, SslProtocols sslProtocol, CipherAlgorithmType cipherAlgorithm, HashAlgorithmType hashAlgorithm, ExchangeAlgorithmType keyExchangeAlgorithm, int keyExchangeStrength);
+#endif
[LoggerMessage(EventIds.CouldNotInitializeTlsConnection, LogLevel.Error, "Session {SessionId} couldn't initialize TLS connection")]
public static partial void CouldNotInitializeTlsConnection(ILogger logger, Exception exception, string sessionId);
@@ -216,10 +222,10 @@ internal static partial class Log
#if NETCOREAPP3_0_OR_GREATER
[LoggerMessage(EventIds.ConnectedTlsBasicPreliminary, LogLevel.Debug, "Session {SessionId} provisionally connected TLS with error {SslPolicyErrors} using {SslProtocol} and {NegotiatedCipherSuite}")]
public static partial void ConnectedTlsBasicPreliminary(ILogger logger, string sessionId, SslPolicyErrors sslPolicyErrors, SslProtocols sslProtocol, TlsCipherSuite negotiatedCipherSuite);
-#endif
-
+#else
[LoggerMessage(EventIds.ConnectedTlsDetailedPreliminary, LogLevel.Debug, "Session {SessionId} provisionally connected TLS with error {SslPolicyErrors} using {SslProtocol}, {CipherAlgorithm}, {HashAlgorithm}, {KeyExchangeAlgorithm}, {KeyExchangeStrength}")]
public static partial void ConnectedTlsDetailedPreliminary(ILogger logger, string sessionId, SslPolicyErrors sslPolicyErrors, SslProtocols sslProtocol, CipherAlgorithmType cipherAlgorithm, HashAlgorithmType hashAlgorithm, ExchangeAlgorithmType keyExchangeAlgorithm, int keyExchangeStrength);
+#endif
[LoggerMessage(EventIds.CertificateErrorUnixSocket, LogLevel.Trace, "Session {SessionId} ignoring remote certificate error {SslPolicyErrors} due to Unix socket connection")]
public static partial void CertificateErrorUnixSocket(ILogger logger, string sessionId, SslPolicyErrors sslPolicyErrors);
diff --git a/src/SingleStoreConnector/Protocol/Payloads/HandshakeResponse41Payload.cs b/src/SingleStoreConnector/Protocol/Payloads/HandshakeResponse41Payload.cs
index 55507bdf9..4c1cc5f7c 100644
--- a/src/SingleStoreConnector/Protocol/Payloads/HandshakeResponse41Payload.cs
+++ b/src/SingleStoreConnector/Protocol/Payloads/HandshakeResponse41Payload.cs
@@ -1,5 +1,6 @@
using SingleStoreConnector.Core;
using SingleStoreConnector.Protocol.Serialization;
+using SingleStoreConnector.Utilities;
namespace SingleStoreConnector.Protocol.Payloads;
@@ -56,12 +57,12 @@ private static ByteBufferWriter CreateCapabilitiesPayload(ProtocolCapabilities s
public static PayloadData CreateWithSsl(ProtocolCapabilities serverCapabilities, ConnectionSettings cs, bool useCompression, CharacterSet characterSet) =>
CreateCapabilitiesPayload(serverCapabilities, cs, useCompression, characterSet, ProtocolCapabilities.Ssl).ToPayloadData();
- public static PayloadData Create(InitialHandshakePayload handshake, ConnectionSettings cs, string password, bool useCompression, CharacterSet characterSet, byte[]? connectionAttributes)
+ public static PayloadData Create(InitialHandshakePayload handshake, ConnectionSettings cs, byte[] authenticationResponse, bool useCompression, CharacterSet characterSet, byte[]? connectionAttributes)
{
// TODO: verify server capabilities
var writer = CreateCapabilitiesPayload(handshake.ProtocolCapabilities, cs, useCompression, characterSet);
writer.WriteNullTerminatedString(cs.UserID);
- var authenticationResponse = AuthenticationUtility.CreateAuthenticationResponse(handshake.AuthPluginData, password);
+
writer.Write((byte) authenticationResponse.Length);
writer.Write(authenticationResponse);
@@ -69,7 +70,7 @@ public static PayloadData Create(InitialHandshakePayload handshake, ConnectionSe
writer.WriteNullTerminatedString(cs.Database);
if ((handshake.ProtocolCapabilities & ProtocolCapabilities.PluginAuth) != 0)
- writer.Write("mysql_native_password\0"u8);
+ writer.Write(handshake.AuthPluginName == "caching_sha2_password" ? "caching_sha2_password\0"u8 : "mysql_native_password\0"u8);
if (connectionAttributes is not null)
writer.Write(connectionAttributes);
diff --git a/src/SingleStoreConnector/Protocol/Serialization/AuthenticationUtility.cs b/src/SingleStoreConnector/Protocol/Serialization/AuthenticationUtility.cs
index fb1dd8e4f..f4b7eb834 100644
--- a/src/SingleStoreConnector/Protocol/Serialization/AuthenticationUtility.cs
+++ b/src/SingleStoreConnector/Protocol/Serialization/AuthenticationUtility.cs
@@ -24,24 +24,26 @@ public static byte[] GetNullTerminatedPasswordBytes(string password)
return passwordBytes;
}
- public static byte[] CreateAuthenticationResponse(ReadOnlySpan challenge, string password) =>
- string.IsNullOrEmpty(password) ? [] : HashPassword(challenge, password, onlyHashPassword: false);
-
///
/// Hashes a password with the "Secure Password Authentication" method.
///
- /// The 20-byte random challenge (from the "auth-plugin-data" in the initial handshake).
/// The password to hash.
- /// If true, is ignored and only the twice-hashed password
- /// is returned, instead of performing the full "secure password authentication" algorithm that XORs the hashed password against
- /// a hash derived from the challenge.
- /// A 20-byte password hash.
+ /// The 20-byte random challenge (from the "auth-plugin-data" in the initial handshake).
+ /// The authentication response.
+ /// The twice-hashed password.
/// See Secure Password Authentication.
#if NET5_0_OR_GREATER
[SkipLocalsInit]
#endif
- public static byte[] HashPassword(ReadOnlySpan challenge, string password, bool onlyHashPassword)
+ public static void CreateResponseAndPasswordHash(string password, ReadOnlySpan authenticationData, out byte[] authenticationResponse, out byte[] passwordHash)
{
+ if (string.IsNullOrEmpty(password))
+ {
+ authenticationResponse = [];
+ passwordHash = [];
+ return;
+ }
+
#if !NET5_0_OR_GREATER
using var sha1 = SHA1.Create();
#endif
@@ -58,10 +60,9 @@ public static byte[] HashPassword(ReadOnlySpan challenge, string password,
sha1.TryComputeHash(passwordBytes, hashedPassword, out _);
sha1.TryComputeHash(hashedPassword, combined[20..], out _);
#endif
- if (onlyHashPassword)
- return combined[20..].ToArray();
+ passwordHash = combined[20..].ToArray();
- challenge[..20].CopyTo(combined);
+ authenticationData[..20].CopyTo(combined);
Span xorBytes = stackalloc byte[20];
#if NET5_0_OR_GREATER
SHA1.TryHashData(combined, xorBytes, out _);
@@ -71,7 +72,7 @@ public static byte[] HashPassword(ReadOnlySpan challenge, string password,
for (var i = 0; i < hashedPassword.Length; i++)
hashedPassword[i] ^= xorBytes[i];
- return hashedPassword.ToArray();
+ authenticationResponse = hashedPassword.ToArray();
}
public static byte[] CreateScrambleResponse(ReadOnlySpan nonce, string password) =>
diff --git a/src/SingleStoreConnector/Protocol/Serialization/ByteArrayReader.cs b/src/SingleStoreConnector/Protocol/Serialization/ByteArrayReader.cs
index 9db9a3631..89607e8c7 100644
--- a/src/SingleStoreConnector/Protocol/Serialization/ByteArrayReader.cs
+++ b/src/SingleStoreConnector/Protocol/Serialization/ByteArrayReader.cs
@@ -16,13 +16,9 @@ public int Offset
readonly get => m_offset;
set
{
-#if NET8_0_OR_GREATER
ArgumentOutOfRangeException.ThrowIfNegative(value);
ArgumentOutOfRangeException.ThrowIfGreaterThan(value, m_maxOffset);
-#else
- if (value < 0 || value > m_maxOffset)
- throw new ArgumentOutOfRangeException(nameof(value), $"value must be between 0 and {m_maxOffset:d}");
-#endif
+
m_offset = value;
}
}
@@ -73,13 +69,8 @@ public uint ReadUInt32()
public uint ReadFixedLengthUInt32(int length)
{
-#if NET8_0_OR_GREATER
ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(length, 0);
ArgumentOutOfRangeException.ThrowIfGreaterThan(length, 4);
-#else
- if (length is <= 0 or > 4)
- throw new ArgumentOutOfRangeException(nameof(length));
-#endif
VerifyRead(length);
uint result = 0;
for (var i = 0; i < length; i++)
@@ -90,13 +81,8 @@ public uint ReadFixedLengthUInt32(int length)
public ulong ReadFixedLengthUInt64(int length)
{
-#if NET8_0_OR_GREATER
ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(length, 0);
ArgumentOutOfRangeException.ThrowIfGreaterThan(length, 8);
-#else
- if (length is <= 0 or > 8)
- throw new ArgumentOutOfRangeException(nameof(length));
-#endif
VerifyRead(length);
ulong result = 0;
for (var i = 0; i < length; i++)
diff --git a/src/SingleStoreConnector/Protocol/Serialization/StandardPayloadHandler.cs b/src/SingleStoreConnector/Protocol/Serialization/StandardPayloadHandler.cs
index 790238c2b..ecbceec6c 100644
--- a/src/SingleStoreConnector/Protocol/Serialization/StandardPayloadHandler.cs
+++ b/src/SingleStoreConnector/Protocol/Serialization/StandardPayloadHandler.cs
@@ -37,12 +37,7 @@ public IByteHandler ByteHandler
set
{
var oldByteHandler = m_byteHandler;
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(value);
-#else
- if (value is null)
- throw new ArgumentNullException(nameof(value));
-#endif
m_byteHandler = value;
oldByteHandler?.Dispose();
m_bufferedByteReader = new();
diff --git a/src/SingleStoreConnector/SingleStoreAttributeCollection.cs b/src/SingleStoreConnector/SingleStoreAttributeCollection.cs
index 332a8916d..dbb8774d8 100644
--- a/src/SingleStoreConnector/SingleStoreAttributeCollection.cs
+++ b/src/SingleStoreConnector/SingleStoreAttributeCollection.cs
@@ -19,18 +19,9 @@ public sealed class SingleStoreAttributeCollection : IEnumerableThe attribute name must not be empty, and must not already exist in the collection.
public void Add(SingleStoreAttribute attribute)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(attribute);
-#else
- if (attribute is null)
- throw new ArgumentNullException(nameof(attribute));
-#endif
-#if NET8_0_OR_GREATER
ArgumentException.ThrowIfNullOrEmpty(attribute.AttributeName);
-#else
- if (string.IsNullOrEmpty(attribute.AttributeName))
- throw new ArgumentException("Attribute name must not be empty", nameof(attribute));
-#endif
+
foreach (var existingAttribute in m_attributes)
{
if (existingAttribute.AttributeName == attribute.AttributeName)
@@ -46,12 +37,8 @@ public void Add(SingleStoreAttribute attribute)
/// The attribute value.
public void SetAttribute(string attributeName, object? value)
{
-#if NET8_0_OR_GREATER
ArgumentException.ThrowIfNullOrEmpty(attributeName);
-#else
- if (string.IsNullOrEmpty(attributeName))
- throw new ArgumentException("Attribute name must not be empty", nameof(attributeName));
-#endif
+
for (var i = 0; i < m_attributes.Count; i++)
{
if (m_attributes[i].AttributeName == attributeName)
diff --git a/src/SingleStoreConnector/SingleStoreBatch.cs b/src/SingleStoreConnector/SingleStoreBatch.cs
index 8f0c4cb79..c51add71e 100644
--- a/src/SingleStoreConnector/SingleStoreBatch.cs
+++ b/src/SingleStoreConnector/SingleStoreBatch.cs
@@ -14,7 +14,7 @@ namespace SingleStoreConnector;
/// individually.
/// Example usage:
///
-/// using var connection = new SingleStoreConnection("...connection string...");
+/// await using var connection = new SingleStoreConnection("...connection string...");
/// await connection.OpenAsync();
///
/// using var batch = new SingleStoreBatch(connection)
@@ -199,10 +199,10 @@ public Task ExecuteNonQueryAsync(CancellationToken cancellationToken = defa
#endif
int Timeout
{
- get => m_timeout;
+ get;
set
{
- m_timeout = value;
+ field = value;
((ICancellableCommand) this).EffectiveCommandTimeout = null;
}
}
@@ -412,7 +412,6 @@ private bool IsPrepared
private readonly int m_commandId;
private bool m_isDisposed;
- private int m_timeout;
private Action? m_cancelAction;
private Action? m_cancelForCommandTimeoutAction;
private uint m_cancelTimerId;
diff --git a/src/SingleStoreConnector/SingleStoreBulkCopy.cs b/src/SingleStoreConnector/SingleStoreBulkCopy.cs
index 36fe934df..a7b079ca1 100644
--- a/src/SingleStoreConnector/SingleStoreBulkCopy.cs
+++ b/src/SingleStoreConnector/SingleStoreBulkCopy.cs
@@ -27,7 +27,7 @@ namespace SingleStoreConnector;
/// var dataTable = GetDataTableFromExternalSource();
///
/// // open the connection
-/// using var connection = new SingleStoreConnection("...;AllowLoadLocalInfile=True");
+/// await using var connection = new SingleStoreConnection("...;AllowLoadLocalInfile=True");
/// await connection.OpenAsync();
///
/// // bulk copy the data
@@ -52,12 +52,7 @@ public sealed class SingleStoreBulkCopy
/// (Optional) The to use.
public SingleStoreBulkCopy(SingleStoreConnection connection, SingleStoreTransaction? transaction = null)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(connection);
-#else
- if (connection is null)
- throw new ArgumentNullException(nameof(connection));
-#endif
m_connection = connection;
m_transaction = transaction;
m_logger = m_connection.LoggingConfiguration.BulkCopyLogger;
@@ -117,12 +112,7 @@ public SingleStoreBulkCopy(SingleStoreConnection connection, SingleStoreTransact
/// A with the result of the bulk copy operation.
public SingleStoreBulkCopyResult WriteToServer(DataTable dataTable)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(dataTable);
-#else
- if (dataTable is null)
- throw new ArgumentNullException(nameof(dataTable));
-#endif
m_valuesEnumerator = DataRowsValuesEnumerator.Create(dataTable);
#pragma warning disable CA2012 // Safe because method completes synchronously
return WriteToServerAsync(IOBehavior.Synchronous, CancellationToken.None).GetAwaiter().GetResult();
@@ -138,12 +128,7 @@ public SingleStoreBulkCopyResult WriteToServer(DataTable dataTable)
/// A with the result of the bulk copy operation.
public async ValueTask WriteToServerAsync(DataTable dataTable, CancellationToken cancellationToken = default)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(dataTable);
-#else
- if (dataTable is null)
- throw new ArgumentNullException(nameof(dataTable));
-#endif
m_valuesEnumerator = DataRowsValuesEnumerator.Create(dataTable);
return await WriteToServerAsync(IOBehavior.Asynchronous, cancellationToken).ConfigureAwait(false);
}
@@ -158,12 +143,7 @@ public async ValueTask WriteToServerAsync(DataTable d
/// A with the result of the bulk copy operation.
public SingleStoreBulkCopyResult WriteToServer(IEnumerable dataRows, int columnCount)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(dataRows);
-#else
- if (dataRows is null)
- throw new ArgumentNullException(nameof(dataRows));
-#endif
m_valuesEnumerator = new DataRowsValuesEnumerator(dataRows, columnCount);
#pragma warning disable CA2012 // Safe because method completes synchronously
return WriteToServerAsync(IOBehavior.Synchronous, CancellationToken.None).GetAwaiter().GetResult();
@@ -181,12 +161,7 @@ public SingleStoreBulkCopyResult WriteToServer(IEnumerable dataRows, in
/// A with the result of the bulk copy operation.
public async ValueTask WriteToServerAsync(IEnumerable dataRows, int columnCount, CancellationToken cancellationToken = default)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(dataRows);
-#else
- if (dataRows is null)
- throw new ArgumentNullException(nameof(dataRows));
-#endif
m_valuesEnumerator = new DataRowsValuesEnumerator(dataRows, columnCount);
return await WriteToServerAsync(IOBehavior.Asynchronous, cancellationToken).ConfigureAwait(false);
}
@@ -199,12 +174,7 @@ public async ValueTask WriteToServerAsync(IEnumerable
/// A with the result of the bulk copy operation.
public SingleStoreBulkCopyResult WriteToServer(IDataReader dataReader)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(dataReader);
-#else
- if (dataReader is null)
- throw new ArgumentNullException(nameof(dataReader));
-#endif
m_valuesEnumerator = DataReaderValuesEnumerator.Create(dataReader);
#pragma warning disable CA2012 // Safe because method completes synchronously
return WriteToServerAsync(IOBehavior.Synchronous, CancellationToken.None).GetAwaiter().GetResult();
@@ -220,12 +190,7 @@ public SingleStoreBulkCopyResult WriteToServer(IDataReader dataReader)
/// A with the result of the bulk copy operation.
public async ValueTask WriteToServerAsync(IDataReader dataReader, CancellationToken cancellationToken = default)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(dataReader);
-#else
- if (dataReader is null)
- throw new ArgumentNullException(nameof(dataReader));
-#endif
m_valuesEnumerator = DataReaderValuesEnumerator.Create(dataReader);
return await WriteToServerAsync(IOBehavior.Asynchronous, cancellationToken).ConfigureAwait(false);
}
@@ -266,27 +231,30 @@ private async ValueTask WriteToServerAsync(IOBehavior
using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SchemaOnly, ioBehavior, cancellationToken).ConfigureAwait(false))
{
var schema = reader.GetColumnSchema();
- for (var i = 0; i < Math.Min(m_valuesEnumerator!.FieldCount, schema.Count); i++)
+ for (var i = 0; i < schema.Count; i++)
{
var destinationColumn = reader.GetName(i);
- if (schema[i].DataTypeName == "BIT")
+ var dataTypeName = schema[i].DataTypeName;
+ if (dataTypeName == "BIT")
{
AddColumnMapping(m_logger, columnMappings, addDefaultMappings, i, destinationColumn, $"@`temporary_column_dotnet_connector_col{i}`", $"%COL% = CAST(%VAR% AS UNSIGNED)");
}
- else if (schema[i].DataTypeName == "YEAR")
- {
- // the current code can't distinguish between 0 = 0000 and 0 = 2000
- throw new NotSupportedException("'YEAR' columns are not supported by SingleStoreBulkLoader.");
- }
else
{
var type = schema[i].DataType;
- if (type == typeof(byte[]) || (type == typeof(Guid) && (m_connection.GuidFormat is SingleStoreGuidFormat.Binary16 or SingleStoreGuidFormat.LittleEndianBinary16 or SingleStoreGuidFormat.TimeSwapBinary16)))
+ if (type == typeof(byte[]) ||
+ (type == typeof(Guid) && (m_connection.GuidFormat is SingleStoreGuidFormat.Binary16 or SingleStoreGuidFormat.LittleEndianBinary16 or SingleStoreGuidFormat.TimeSwapBinary16)))
{
AddColumnMapping(m_logger, columnMappings, addDefaultMappings, i, destinationColumn, $"@`temporary_column_dotnet_connector_col{i}`", $"%COL% = UNHEX(%VAR%)");
}
else if (addDefaultMappings)
{
+ if (schema[i].DataTypeName == "YEAR")
+ {
+ // the current code can't distinguish between 0 = 0000 and 0 = 2000
+ throw new NotSupportedException("'YEAR' columns are not supported by SingleStoreBulkCopy.");
+ }
+
Log.AddingDefaultColumnMapping(m_logger, i, destinationColumn);
columnMappings.Add(new(i, destinationColumn));
}
@@ -295,7 +263,7 @@ private async ValueTask WriteToServerAsync(IOBehavior
}
// set columns and expressions from the column mappings
- for (var i = 0; i < m_valuesEnumerator.FieldCount; i++)
+ for (var i = 0; i < m_valuesEnumerator!.FieldCount; i++)
{
var columnMapping = columnMappings.FirstOrDefault(x => x.SourceOrdinal == i);
if (columnMapping is null)
@@ -505,13 +473,16 @@ static bool WriteValue(SingleStoreConnection connection, object value, ref int i
{
return Utf8Formatter.TryFormat(decimalValue, output, out bytesWritten);
}
- else if (value is byte[] or ReadOnlyMemory or Memory or ArraySegment)
+ else if (value is byte[] or ReadOnlyMemory or Memory or ArraySegment or float[] or ReadOnlyMemory or Memory)
{
var inputSpan = value switch
{
byte[] byteArray => byteArray.AsSpan(),
ArraySegment arraySegment => arraySegment.AsSpan(),
Memory memory => memory.Span,
+ float[] floatArray => SingleStoreParameter.ConvertFloatsToBytes(floatArray.AsSpan()),
+ Memory memory => SingleStoreParameter.ConvertFloatsToBytes(memory.Span),
+ ReadOnlyMemory memory => SingleStoreParameter.ConvertFloatsToBytes(memory.Span),
_ => ((ReadOnlyMemory) value).Span,
};
diff --git a/src/SingleStoreConnector/SingleStoreBulkLoader.cs b/src/SingleStoreConnector/SingleStoreBulkLoader.cs
index 5945fe39c..8bd222a53 100644
--- a/src/SingleStoreConnector/SingleStoreBulkLoader.cs
+++ b/src/SingleStoreConnector/SingleStoreBulkLoader.cs
@@ -9,7 +9,7 @@ namespace SingleStoreConnector;
/// lets you efficiently load a SingleStore Server Table with data from a CSV or TSV file or .
/// Example code:
///
-/// using var connection = new SingleStoreConnection("...;AllowLoadLocalInfile=True");
+/// await using var connection = new SingleStoreConnection("...;AllowLoadLocalInfile=True");
/// await connection.OpenAsync();
/// var bulkLoader = new SingleStoreBulkLoader(connection)
/// {
@@ -183,7 +183,7 @@ internal async ValueTask LoadAsync(IOBehavior ioBehavior, CancellationToken
else
{
if (!Local)
- throw new InvalidOperationException("Local must be true to use SourceStream, SourceDataTable, or SourceDataReader.");
+ throw new InvalidOperationException("Local must be true to use SourceStream.");
FileName = GenerateSourceFileName();
AddSource(FileName, Source!);
diff --git a/src/SingleStoreConnector/SingleStoreCommand.cs b/src/SingleStoreConnector/SingleStoreCommand.cs
index 9c5e63875..7072237c9 100644
--- a/src/SingleStoreConnector/SingleStoreCommand.cs
+++ b/src/SingleStoreConnector/SingleStoreCommand.cs
@@ -59,7 +59,7 @@ public SingleStoreCommand(string? commandText, SingleStoreConnection? connection
{
GC.SuppressFinalize(this);
m_commandId = ICancellableCommandExtensions.GetNextId();
- m_commandText = commandText ?? "";
+ CommandText = commandText ?? "";
Connection = connection;
Transaction = transaction;
CommandType = CommandType.Text;
@@ -71,7 +71,7 @@ private SingleStoreCommand(SingleStoreCommand other)
GC.SuppressFinalize(this);
m_commandTimeout = other.m_commandTimeout;
((ICancellableCommand) this).EffectiveCommandTimeout = null;
- m_commandType = other.m_commandType;
+ CommandType = other.CommandType;
DesignTimeVisible = other.DesignTimeVisible;
UpdatedRowSource = other.UpdatedRowSource;
m_parameterCollection = other.CloneRawParameters();
@@ -200,12 +200,12 @@ private bool NeedsPrepare(out Exception? exception)
[AllowNull]
public override string CommandText
{
- get => m_commandText;
+ get;
set
{
- if (m_connection?.ActiveCommandId == m_commandId)
+ if (Connection?.ActiveCommandId == m_commandId)
throw new InvalidOperationException("Cannot set SingleStoreCommand.CommandText when there is an open DataReader for this command; it must be closed first.");
- m_commandText = value ?? "";
+ field = value ?? "";
}
}
@@ -215,12 +215,12 @@ public override string CommandText
public new SingleStoreConnection? Connection
{
- get => m_connection;
+ get;
set
{
- if (m_connection?.ActiveCommandId == m_commandId)
+ if (field?.ActiveCommandId == m_commandId)
throw new InvalidOperationException("Cannot set SingleStoreCommand.Connection when there is an open DataReader for this command; it must be closed first.");
- m_connection = value;
+ field = value;
}
}
@@ -230,12 +230,7 @@ public override int CommandTimeout
get => Math.Min(m_commandTimeout ?? Connection?.DefaultCommandTimeout ?? 0, int.MaxValue / 1000);
set
{
-#if NET8_0_OR_GREATER
ArgumentOutOfRangeException.ThrowIfNegative(value);
-#else
- if (value < 0)
- throw new ArgumentOutOfRangeException(nameof(value), "CommandTimeout must be greater than or equal to zero.");
-#endif
m_commandTimeout = value;
((ICancellableCommand) this).EffectiveCommandTimeout = null;
}
@@ -244,12 +239,12 @@ public override int CommandTimeout
///
public override CommandType CommandType
{
- get => m_commandType;
+ get;
set
{
if (value is not CommandType.Text and not CommandType.StoredProcedure)
throw new ArgumentException("CommandType must be Text or StoredProcedure.", nameof(value));
- m_commandType = value;
+ field = value;
}
}
@@ -454,8 +449,8 @@ private bool IsValid([NotNullWhen(false)] out Exception? exception)
return exception is null;
}
- PreparedStatements? ISingleStoreCommand.TryGetPreparedStatements() => CommandType == CommandType.Text && !string.IsNullOrWhiteSpace(CommandText) && m_connection is not null &&
- m_connection.State == ConnectionState.Open ? m_connection.Session.TryGetPreparedStatement(CommandText!) : null;
+ PreparedStatements? ISingleStoreCommand.TryGetPreparedStatements() => CommandType == CommandType.Text && !string.IsNullOrWhiteSpace(CommandText) && Connection is not null &&
+ Connection.State == ConnectionState.Open ? Connection.Session.TryGetPreparedStatement(CommandText!) : null;
CommandBehavior ISingleStoreCommand.CommandBehavior => m_commandBehavior;
SingleStoreParameterCollection? ISingleStoreCommand.OutParameters { get; set; }
@@ -464,12 +459,9 @@ private bool IsValid([NotNullWhen(false)] out Exception? exception)
private readonly int m_commandId;
private bool m_isDisposed;
- private SingleStoreConnection? m_connection;
- private string m_commandText;
private SingleStoreParameterCollection? m_parameterCollection;
private SingleStoreAttributeCollection? m_attributeCollection;
private int? m_commandTimeout;
- private CommandType m_commandType;
private CommandBehavior m_commandBehavior;
private Action? m_cancelAction;
private Action? m_cancelForCommandTimeoutAction;
diff --git a/src/SingleStoreConnector/SingleStoreCommandBuilder.cs b/src/SingleStoreConnector/SingleStoreCommandBuilder.cs
index 6a96df703..1feb9b280 100644
--- a/src/SingleStoreConnector/SingleStoreCommandBuilder.cs
+++ b/src/SingleStoreConnector/SingleStoreCommandBuilder.cs
@@ -12,12 +12,7 @@ public sealed class SingleStoreCommandBuilder : DbCommandBuilder
private static async Task DeriveParametersAsync(IOBehavior ioBehavior, SingleStoreCommand command, CancellationToken cancellationToken)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(command);
-#else
- if (command is null)
- throw new ArgumentNullException(nameof(command));
-#endif
if (command.CommandType != CommandType.StoredProcedure)
throw new ArgumentException($"SingleStoreCommand.CommandType must be StoredProcedure not {command.CommandType}", nameof(command));
if (string.IsNullOrWhiteSpace(command.CommandText))
diff --git a/src/SingleStoreConnector/SingleStoreConnection.cs b/src/SingleStoreConnector/SingleStoreConnection.cs
index 0e11c4d5a..fcb2e8e7a 100644
--- a/src/SingleStoreConnector/SingleStoreConnection.cs
+++ b/src/SingleStoreConnector/SingleStoreConnection.cs
@@ -450,12 +450,7 @@ internal void UnenlistTransaction()
private void TakeSessionFrom(SingleStoreConnection other)
{
#if DEBUG
-#if NET6_0_OR_GREATER
- ArgumentNullException.ThrowIfNull(other);
-#else
- if (other is null)
- throw new ArgumentNullException(nameof(other));
-#endif
+ ArgumentNullException.ThrowIfNull(other);
if (m_session is not null)
throw new InvalidOperationException("This connection must not have a session");
if (other.m_session is null)
@@ -537,7 +532,7 @@ private async ValueTask PingAsync(IOBehavior ioBehavior, CancellationToken
{
}
- SetState(ConnectionState.Closed);
+ SetState(ConnectionState.Broken);
return false;
}
@@ -749,12 +744,7 @@ public static Task ClearAllPoolsAsync(CancellationToken cancellationToken = defa
private static async Task ClearPoolAsync(SingleStoreConnection connection, IOBehavior ioBehavior,
CancellationToken cancellationToken)
{
-#if NET6_0_OR_GREATER
- ArgumentNullException.ThrowIfNull(connection);
-#else
- if (connection is null)
- throw new ArgumentNullException(nameof(connection));
-#endif
+ ArgumentNullException.ThrowIfNull(connection);
var pool = ConnectionPool.GetPool(connection.m_connectionString, null, createIfNotFound: false);
if (pool is not null)
@@ -1102,12 +1092,7 @@ internal void Cancel(ICancellableCommand command, int commandId, bool isCancel)
internal void SetActiveReader(SingleStoreDataReader dataReader)
{
-#if NET6_0_OR_GREATER
- ArgumentNullException.ThrowIfNull(dataReader);
-#else
- if (dataReader is null)
- throw new ArgumentNullException(nameof(dataReader));
-#endif
+ ArgumentNullException.ThrowIfNull(dataReader);
if (m_activeReader is not null)
throw new InvalidOperationException("Can't replace active reader.");
m_activeReader = dataReader;
@@ -1151,8 +1136,7 @@ private async ValueTask CreateSessionAsync(ConnectionPool? pool,
// (from the connection string, if non-zero), or a combination of both
if (connectionSettings.ConnectionTimeout != 0)
timeoutSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(Math.Max(1,
- connectionSettings.ConnectionTimeoutMilliseconds -
- Utility.GetElapsedMilliseconds(startingTimestamp))));
+ connectionSettings.ConnectionTimeoutMilliseconds - Utility.GetElapsedMilliseconds(startingTimestamp))));
if (cancellationToken.CanBeCanceled && timeoutSource is not null)
linkedSource =
CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutSource.Token);
@@ -1219,6 +1203,8 @@ private async ValueTask CreateSessionAsync(ConnectionPool? pool,
internal IPEndPoint? SessionEndPoint => m_session!.IPEndPoint;
+ internal SingleStoreDataSource? SingleStoreDataSource => m_dataSource;
+
internal void SetState(ConnectionState newState)
{
if (m_connectionState != newState)
diff --git a/src/SingleStoreConnector/SingleStoreConnectionStringBuilder.cs b/src/SingleStoreConnector/SingleStoreConnectionStringBuilder.cs
index 7e2701f64..6f117d1c7 100644
--- a/src/SingleStoreConnector/SingleStoreConnectionStringBuilder.cs
+++ b/src/SingleStoreConnector/SingleStoreConnectionStringBuilder.cs
@@ -346,7 +346,7 @@ public bool ConnectionReset
[Category("Obsolete")]
[DefaultValue(true)]
[DisplayName("Defer Connection Reset")]
- [Obsolete("This option is no longer supported in MySqlConnector >= 1.4.0.")]
+ [Obsolete("This option is no longer supported in SingleStoreConnector >= 1.4.0.")]
public bool DeferConnectionReset
{
get => SingleStoreConnectionStringOption.DeferConnectionReset.GetValue(this);
diff --git a/src/SingleStoreConnector/SingleStoreConnector.csproj b/src/SingleStoreConnector/SingleStoreConnector.csproj
index 64bc461c0..5d796a70f 100644
--- a/src/SingleStoreConnector/SingleStoreConnector.csproj
+++ b/src/SingleStoreConnector/SingleStoreConnector.csproj
@@ -1,10 +1,10 @@
- net462;net471;net48;netstandard2.0;netstandard2.1;net6.0;net8.0;net9.0
+ net462;net471;net48;netstandard2.0;netstandard2.1;net6.0;net8.0;net9.0;net10.0
A truly async SingleStore ADO.NET provider.
Copyright 2016–2021 Bradley Grainger
- Copyright 2022-2025 SingleStore Inc.
+ Copyright 2022-2026 SingleStore Inc.
SingleStore Inc.
SingleStoreConnector
SingleStoreConnector
diff --git a/src/SingleStoreConnector/SingleStoreConnectorTracingOptions.cs b/src/SingleStoreConnector/SingleStoreConnectorTracingOptions.cs
new file mode 100644
index 000000000..ac979dbbd
--- /dev/null
+++ b/src/SingleStoreConnector/SingleStoreConnectorTracingOptions.cs
@@ -0,0 +1,8 @@
+namespace SingleStoreConnector;
+
+internal sealed class SingleStoreConnectorTracingOptions
+{
+ public bool EnableResultSetHeaderEvent { get; set; }
+
+ public static SingleStoreConnectorTracingOptions Default { get; } = new();
+}
diff --git a/src/SingleStoreConnector/SingleStoreConnectorTracingOptionsBuilder.cs b/src/SingleStoreConnector/SingleStoreConnectorTracingOptionsBuilder.cs
new file mode 100644
index 000000000..05a74f792
--- /dev/null
+++ b/src/SingleStoreConnector/SingleStoreConnectorTracingOptionsBuilder.cs
@@ -0,0 +1,25 @@
+namespace SingleStoreConnector;
+
+///
+/// provides an API for configuring OpenTelemetry tracing options.
+///
+public sealed class SingleStoreConnectorTracingOptionsBuilder
+{
+ ///
+ /// Gets or sets a value indicating whether to enable the "read-result-set-header" event.
+ /// Default is false; set to true to opt in to this event.
+ ///
+ public SingleStoreConnectorTracingOptionsBuilder EnableResultSetHeaderEvent(bool enable = true)
+ {
+ m_enableResultSetHeaderEvent = enable;
+ return this;
+ }
+
+ internal SingleStoreConnectorTracingOptions Build() =>
+ new()
+ {
+ EnableResultSetHeaderEvent = m_enableResultSetHeaderEvent,
+ };
+
+ private bool m_enableResultSetHeaderEvent = SingleStoreConnectorTracingOptions.Default.EnableResultSetHeaderEvent;
+}
diff --git a/src/SingleStoreConnector/SingleStoreDataReader.cs b/src/SingleStoreConnector/SingleStoreDataReader.cs
index 3a5def519..92ed68a5a 100644
--- a/src/SingleStoreConnector/SingleStoreDataReader.cs
+++ b/src/SingleStoreConnector/SingleStoreDataReader.cs
@@ -651,7 +651,7 @@ internal async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancel
if ((m_behavior & CommandBehavior.CloseConnection) != 0)
await connection.CloseAsync(ioBehavior).ConfigureAwait(false);
- // clear fields (so that MySqlConnection can be GCed if the user doesn't hold a reference to it)
+ // clear fields (so that SingleStoreConnection can be GCed if the user doesn't hold a reference to it)
Command = null;
m_commandListPosition = default;
m_payloadCreator = null;
@@ -677,7 +677,7 @@ private static async Task ReadOutParametersAsync(ISingleStoreCommand command, Re
if (param.HasSetDbType && !row.IsDBNull(columnIndex))
{
var dbTypeMapping = TypeMapper.Instance.GetDbTypeMapping(param.DbType);
- if (dbTypeMapping is not null)
+ if (dbTypeMapping is not null && param.DbType is not DbType.Object)
{
param.Value = dbTypeMapping.DoConversion(row.GetValue(columnIndex));
continue;
diff --git a/src/SingleStoreConnector/SingleStoreDataSource.cs b/src/SingleStoreConnector/SingleStoreDataSource.cs
index 61287487c..4954f0123 100644
--- a/src/SingleStoreConnector/SingleStoreDataSource.cs
+++ b/src/SingleStoreConnector/SingleStoreDataSource.cs
@@ -19,12 +19,13 @@ public sealed class SingleStoreDataSource : DbDataSource
/// Thrown if is null.
public SingleStoreDataSource(string connectionString)
: this(connectionString ?? throw new ArgumentNullException(nameof(connectionString)),
- SingleStoreConnectorLoggingConfiguration.NullConfiguration, null, null, null, null, default, default, default)
+ SingleStoreConnectorLoggingConfiguration.NullConfiguration, null, null, null, null, null, default, default, default)
{
}
internal SingleStoreDataSource(string connectionString,
SingleStoreConnectorLoggingConfiguration loggingConfiguration,
+ SingleStoreConnectorTracingOptions? tracingOptions,
string? name,
Func? clientCertificatesCallback,
RemoteCertificateValidationCallback? remoteCertificateValidationCallback,
@@ -35,6 +36,7 @@ internal SingleStoreDataSource(string connectionString,
{
m_connectionString = connectionString;
LoggingConfiguration = loggingConfiguration;
+ TracingOptions = tracingOptions ?? SingleStoreConnectorTracingOptions.Default;
Name = name;
m_clientCertificatesCallback = clientCertificatesCallback;
m_remoteCertificateValidationCallback = remoteCertificateValidationCallback;
@@ -208,6 +210,8 @@ private async Task RefreshPassword()
internal SingleStoreConnectorLoggingConfiguration LoggingConfiguration { get; }
+ internal SingleStoreConnectorTracingOptions TracingOptions { get; }
+
internal string? Name { get; }
private string ProvidePasswordFromField(SingleStoreProvidePasswordContext context) => m_password!;
diff --git a/src/SingleStoreConnector/SingleStoreDataSourceBuilder.cs b/src/SingleStoreConnector/SingleStoreDataSourceBuilder.cs
index e50cefd18..133546068 100644
--- a/src/SingleStoreConnector/SingleStoreDataSourceBuilder.cs
+++ b/src/SingleStoreConnector/SingleStoreDataSourceBuilder.cs
@@ -20,6 +20,18 @@ public SingleStoreDataSourceBuilder(string? connectionString = null)
ConnectionStringBuilder = new(connectionString ?? "");
}
+ ///
+ /// Configures OpenTelemetry tracing options.
+ ///
+ /// This builder, so that method calls can be chained.
+ public SingleStoreDataSourceBuilder ConfigureTracing(Action configureAction)
+ {
+ ArgumentNullException.ThrowIfNull(configureAction);
+ m_tracingOptionsBuilderCallbacks ??= [];
+ m_tracingOptionsBuilderCallbacks.Add(configureAction);
+ return this;
+ }
+
///
/// Sets the that will be used for logging.
///
@@ -106,8 +118,15 @@ public SingleStoreDataSourceBuilder UseConnectionOpenedCallback(SingleStoreConne
public SingleStoreDataSource Build()
{
var loggingConfiguration = m_loggerFactory is null ? SingleStoreConnectorLoggingConfiguration.NullConfiguration : new(m_loggerFactory);
+
+ var tracingOptionsBuilder = new SingleStoreConnectorTracingOptionsBuilder();
+ foreach (var callback in m_tracingOptionsBuilderCallbacks ?? (IEnumerable>) [])
+ callback.Invoke(tracingOptionsBuilder);
+ var tracingOptions = tracingOptionsBuilder.Build();
+
return new(ConnectionStringBuilder.ConnectionString,
loggingConfiguration,
+ tracingOptions,
m_name,
m_clientCertificatesCallback,
m_remoteCertificateValidationCallback,
@@ -131,4 +150,5 @@ public SingleStoreDataSource Build()
private TimeSpan m_periodicPasswordProviderSuccessRefreshInterval;
private TimeSpan m_periodicPasswordProviderFailureRefreshInterval;
private SingleStoreConnectionOpenedCallback? m_connectionOpenedCallback;
+ private List>? m_tracingOptionsBuilderCallbacks;
}
diff --git a/src/SingleStoreConnector/SingleStoreHelper.cs b/src/SingleStoreConnector/SingleStoreHelper.cs
index 3fd6f8871..4d7738f84 100644
--- a/src/SingleStoreConnector/SingleStoreHelper.cs
+++ b/src/SingleStoreConnector/SingleStoreHelper.cs
@@ -12,12 +12,7 @@ public sealed class SingleStoreHelper
///
public static string EscapeString(string value)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(value);
-#else
- if (value is null)
- throw new ArgumentNullException(nameof(value));
-#endif
StringBuilder? sb = null;
int last = -1;
diff --git a/src/SingleStoreConnector/SingleStoreParameter.cs b/src/SingleStoreConnector/SingleStoreParameter.cs
index 72825341a..a32f1c483 100644
--- a/src/SingleStoreConnector/SingleStoreParameter.cs
+++ b/src/SingleStoreConnector/SingleStoreParameter.cs
@@ -1,8 +1,11 @@
+using System.Buffers.Binary;
using System.Buffers.Text;
+using System.Data.Common;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Numerics;
+using System.Runtime.InteropServices;
using System.Text;
#if NET8_0_OR_GREATER
using System.Text.Unicode;
@@ -26,7 +29,7 @@ public SingleStoreParameter(string? name, object? value)
m_name = name ?? "";
NormalizedParameterName = NormalizeParameterName(m_name);
Value = value;
- m_sourceColumn = "";
+ SourceColumn = "";
SourceVersion = DataRowVersion.Current;
}
@@ -46,7 +49,7 @@ public SingleStoreParameter(string name, SingleStoreDbType mySqlDbType, int size
NormalizedParameterName = NormalizeParameterName(m_name);
SingleStoreDbType = mySqlDbType;
Size = size;
- m_sourceColumn = sourceColumn ?? "";
+ SourceColumn = sourceColumn ?? "";
SourceVersion = DataRowVersion.Current;
}
@@ -72,6 +75,7 @@ public override DbType DbType
}
}
+ [DbProviderSpecificTypeProperty(true)]
public SingleStoreDbType SingleStoreDbType
{
get => m_mySqlDbType;
@@ -119,8 +123,8 @@ public override string ParameterName
[AllowNull]
public override string SourceColumn
{
- get => m_sourceColumn;
- set => m_sourceColumn = value ?? "";
+ get;
+ set => field = value ?? "";
}
public override bool SourceColumnNullMapping { get; set; }
@@ -171,7 +175,7 @@ private SingleStoreParameter(SingleStoreParameter other)
m_value = other.m_value;
Precision = other.Precision;
Scale = other.Scale;
- m_sourceColumn = other.m_sourceColumn;
+ SourceColumn = other.SourceColumn;
SourceColumnNullMapping = other.SourceColumnNullMapping;
SourceVersion = other.SourceVersion;
}
@@ -179,12 +183,7 @@ private SingleStoreParameter(SingleStoreParameter other)
private SingleStoreParameter(SingleStoreParameter other, string parameterName)
: this(other)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(parameterName);
-#else
- if (parameterName is null)
- throw new ArgumentNullException(nameof(parameterName));
-#endif
ParameterName = parameterName;
}
@@ -289,7 +288,7 @@ internal void AppendSqlString(ByteBufferWriter writer, StatementPreparerOptions
{
writer.WriteString(ulongValue);
}
- else if (Value is byte[] or ReadOnlyMemory or Memory or ArraySegment or MemoryStream)
+ else if (Value is byte[] or ReadOnlyMemory or Memory or ArraySegment or MemoryStream or float[] or ReadOnlyMemory or Memory)
{
var inputSpan = Value switch
{
@@ -297,6 +296,9 @@ internal void AppendSqlString(ByteBufferWriter writer, StatementPreparerOptions
ArraySegment arraySegment => arraySegment.AsSpan(),
Memory memory => memory.Span,
MemoryStream memoryStream => memoryStream.TryGetBuffer(out var streamBuffer) ? streamBuffer.AsSpan() : memoryStream.ToArray().AsSpan(),
+ float[] floatArray => ConvertFloatsToBytes(floatArray.AsSpan()),
+ Memory memory => ConvertFloatsToBytes(memory.Span),
+ ReadOnlyMemory memory => ConvertFloatsToBytes(memory.Span),
_ => ((ReadOnlyMemory) Value).Span,
};
@@ -340,6 +342,10 @@ internal void AppendSqlString(ByteBufferWriter writer, StatementPreparerOptions
writer.Write('\"' + geographyValue + '\"');
}
+ else if (Value is Stream)
+ {
+ throw new NotSupportedException($"Parameter type {Value.GetType().Name} can only be used after calling SingleStoreCommand.Prepare.");
+ }
else if (Value is bool boolValue)
{
writer.Write(boolValue ? "true"u8 : "false"u8);
@@ -743,13 +749,58 @@ private void AppendBinary(ByteBufferWriter writer, object value, StatementPrepar
writer.WriteLengthEncodedInteger(unchecked((ulong) streamBuffer.Count));
writer.Write(streamBuffer);
}
+ else if (value is Stream)
+ {
+ // do nothing; this will be sent via CommandKind.StatementSendLongData
+ }
else if (value is float floatValue)
{
- writer.Write(BitConverter.GetBytes(floatValue));
+#if NET5_0_OR_GREATER
+ Span bytes = stackalloc byte[4];
+ BinaryPrimitives.WriteSingleLittleEndian(bytes, floatValue);
+ writer.Write(bytes);
+#else
+ // convert float to bytes with correct endianness (MySQL uses little-endian)
+ var bytes = BitConverter.GetBytes(floatValue);
+ if (!BitConverter.IsLittleEndian)
+ Array.Reverse(bytes);
+ writer.Write(bytes);
+#endif
}
else if (value is double doubleValue)
{
- writer.Write(unchecked((ulong) BitConverter.DoubleToInt64Bits(doubleValue)));
+#if NET5_0_OR_GREATER
+ Span bytes = stackalloc byte[8];
+ BinaryPrimitives.WriteDoubleLittleEndian(bytes, doubleValue);
+ writer.Write(bytes);
+#else
+ if (BitConverter.IsLittleEndian)
+ {
+ writer.Write(unchecked((ulong) BitConverter.DoubleToInt64Bits(doubleValue)));
+ }
+ else
+ {
+ // convert double to bytes with correct endianness (MySQL uses little-endian)
+ var bytes = BitConverter.GetBytes(doubleValue);
+ Array.Reverse(bytes);
+ writer.Write(bytes);
+ }
+#endif
+ }
+ else if (value is float[] floatArrayValue)
+ {
+ writer.WriteLengthEncodedInteger(unchecked((ulong) floatArrayValue.Length * 4));
+ writer.Write(ConvertFloatsToBytes(floatArrayValue.AsSpan()));
+ }
+ else if (value is Memory floatMemory)
+ {
+ writer.WriteLengthEncodedInteger(unchecked((ulong) floatMemory.Length * 4));
+ writer.Write(ConvertFloatsToBytes(floatMemory.Span));
+ }
+ else if (value is ReadOnlyMemory floatReadOnlyMemory)
+ {
+ writer.WriteLengthEncodedInteger(unchecked((ulong) floatReadOnlyMemory.Length * 4));
+ writer.Write(ConvertFloatsToBytes(floatReadOnlyMemory.Span));
}
else if (value is decimal decimalValue)
{
@@ -968,12 +1019,37 @@ private static void WriteTime(ByteBufferWriter writer, TimeSpan timeSpan)
}
}
+ internal static ReadOnlySpan ConvertFloatsToBytes(ReadOnlySpan floats)
+ {
+ if (BitConverter.IsLittleEndian)
+ {
+ return MemoryMarshal.AsBytes(floats);
+ }
+ else
+ {
+ // for big-endian platforms, we need to convert each float individually
+ var bytes = new byte[floats.Length * 4];
+
+ for (var i = 0; i < floats.Length; i++)
+ {
+#if NET5_0_OR_GREATER
+ BinaryPrimitives.WriteSingleLittleEndian(bytes.AsSpan(i * 4), floats[i]);
+#else
+ var floatBytes = BitConverter.GetBytes(floats[i]);
+ Array.Reverse(floatBytes);
+ floatBytes.CopyTo(bytes, i * 4);
+#endif
+ }
+
+ return bytes;
+ }
+ }
+
private static ReadOnlySpan BinaryBytes => "_binary'"u8;
private DbType m_dbType;
private SingleStoreDbType m_mySqlDbType;
private string m_name;
private ParameterDirection? m_direction;
- private string m_sourceColumn;
private object? m_value;
}
diff --git a/src/SingleStoreConnector/SingleStoreParameterCollection.cs b/src/SingleStoreConnector/SingleStoreParameterCollection.cs
index e5f7eb78a..9bbded24e 100644
--- a/src/SingleStoreConnector/SingleStoreParameterCollection.cs
+++ b/src/SingleStoreConnector/SingleStoreParameterCollection.cs
@@ -25,24 +25,14 @@ public SingleStoreParameter Add(string parameterName, DbType dbType)
public override int Add(object value)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(value);
-#else
- if (value is null)
- throw new ArgumentNullException(nameof(value));
-#endif
AddParameter((SingleStoreParameter) value, m_parameters.Count);
return m_parameters.Count - 1;
}
public SingleStoreParameter Add(SingleStoreParameter parameter)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(parameter);
-#else
- if (parameter is null)
- throw new ArgumentNullException(nameof(parameter));
-#endif
AddParameter(parameter, m_parameters.Count);
return parameter;
}
@@ -135,12 +125,7 @@ public override void RemoveAt(int index)
protected override void SetParameter(int index, DbParameter value)
{
-#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(value);
-#else
- if (value is null)
- throw new ArgumentNullException(nameof(value));
-#endif
var newParameter = (SingleStoreParameter) value;
var oldParameter = m_parameters[index];
if (oldParameter.NormalizedParameterName is not null)
diff --git a/src/SingleStoreConnector/Utilities/Adler32.cs b/src/SingleStoreConnector/Utilities/Adler32.cs
index dcba6cc29..747e6c3fa 100644
--- a/src/SingleStoreConnector/Utilities/Adler32.cs
+++ b/src/SingleStoreConnector/Utilities/Adler32.cs
@@ -9,8 +9,6 @@
using System.Runtime.Intrinsics.X86;
#endif
-#pragma warning disable IDE0007 // Use implicit type
-
namespace SingleStoreConnector.Utilities;
///
diff --git a/src/SingleStoreConnector/Utilities/CallerAttributes.cs b/src/SingleStoreConnector/Utilities/CallerAttributes.cs
new file mode 100644
index 000000000..c550fb87b
--- /dev/null
+++ b/src/SingleStoreConnector/Utilities/CallerAttributes.cs
@@ -0,0 +1,10 @@
+#if !NET6_0_OR_GREATER
+namespace System.Runtime.CompilerServices;
+
+// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CallerArgumentExpressionAttribute.cs
+[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
+internal sealed class CallerArgumentExpressionAttribute(string parameterName) : Attribute
+{
+ public string ParameterName { get; } = parameterName;
+}
+#endif
diff --git a/src/SingleStoreConnector/Utilities/TimerQueue.cs b/src/SingleStoreConnector/Utilities/TimerQueue.cs
index 887a0e9d8..8d0f7f290 100644
--- a/src/SingleStoreConnector/Utilities/TimerQueue.cs
+++ b/src/SingleStoreConnector/Utilities/TimerQueue.cs
@@ -14,12 +14,7 @@ internal sealed class TimerQueue
/// A timer ID that can be passed to to cancel the timer.
public uint Add(int delay, Action action)
{
-#if NET8_0_OR_GREATER
ArgumentOutOfRangeException.ThrowIfNegative(delay);
-#else
- if (delay < 0)
- throw new ArgumentOutOfRangeException(nameof(delay), $"delay must not be negative: {delay}");
-#endif
var current = Environment.TickCount;
lock (m_lock)
diff --git a/src/SingleStoreConnector/Utilities/Utility.cs b/src/SingleStoreConnector/Utilities/Utility.cs
index abb2ef90e..e7f16555b 100644
--- a/src/SingleStoreConnector/Utilities/Utility.cs
+++ b/src/SingleStoreConnector/Utilities/Utility.cs
@@ -88,6 +88,44 @@ public static unsafe int GetByteCount(this Encoder encoder, ReadOnlySpan c
}
#endif
+#if NET5_0_OR_GREATER
+ ///
+ /// Loads a RSA key from PEM bytes.
+ ///
+ public static void LoadRsaParameters(byte[] key, RSA rsa)
+ {
+#if NET10_0_OR_GREATER
+ if (!PemEncoding.TryFindUtf8(key, out var pemFields))
+ throw new FormatException("Unrecognized PEM data: " + Encoding.ASCII.GetString(key.AsSpan(0, Math.Min(key.Length, 80))));
+ var isPrivate = key.AsSpan()[pemFields.Label].SequenceEqual("RSA PRIVATE KEY"u8);
+
+ var keyBytes = key.AsSpan()[pemFields.Base64Data];
+ var bufferLength = keyBytes.Length / 4 * 3;
+ byte[]? buffer = null;
+ Span bufferBytes = bufferLength > 1024 ?
+ (Span) (buffer = ArrayPool.Shared.Rent(bufferLength)) :
+ stackalloc byte[bufferLength];
+ try
+ {
+ if (Base64.DecodeFromUtf8(keyBytes, bufferBytes, out _, out var bytesWritten) != OperationStatus.Done)
+ throw new FormatException("The input is not a valid Base-64 string.");
+ if (isPrivate)
+ rsa.ImportRSAPrivateKey(bufferBytes[..bytesWritten], out var _);
+ else
+ rsa.ImportSubjectPublicKeyInfo(bufferBytes[..bytesWritten], out var _);
+ }
+ finally
+ {
+ if (buffer is not null)
+ ArrayPool.Shared.Return(buffer);
+ }
+#else
+ LoadRsaParameters(Encoding.ASCII.GetString(key), rsa);
+#endif
+ }
+#endif
+
+#if !NET10_0_OR_GREATER
///
/// Loads a RSA key from a PEM string.
///
@@ -97,6 +135,11 @@ public static void LoadRsaParameters(string key, RSA rsa)
public static RSAParameters GetRsaParameters(string key)
#endif
{
+#if NET5_0_OR_GREATER
+ if (!PemEncoding.TryFind(key, out var pemFields))
+ throw new FormatException(string.Concat("Unrecognized PEM data: ", key.AsSpan(0, Math.Min(key.Length, 80))));
+ var isPrivate = key.AsSpan()[pemFields.Label].SequenceEqual("RSA PRIVATE KEY");
+#else
const string beginRsaPrivateKey = "-----BEGIN RSA PRIVATE KEY-----";
const string endRsaPrivateKey = "-----END RSA PRIVATE KEY-----";
const string beginPublicKey = "-----BEGIN PUBLIC KEY-----";
@@ -135,9 +178,14 @@ public static RSAParameters GetRsaParameters(string key)
#else
throw new FormatException($"Missing expected '{pemFooter}' PEM footer: " + key[Math.Max(key.Length - 80, 0)..]);
#endif
+#endif
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+#if NET5_0_OR_GREATER
+ var keyChars = key.AsSpan()[pemFields.Base64Data];
+#else
var keyChars = key.AsSpan()[keyStartIndex..keyEndIndex];
+#endif
var bufferLength = keyChars.Length / 4 * 3;
byte[]? buffer = null;
Span bufferBytes = bufferLength > 1024 ?
@@ -166,6 +214,7 @@ public static RSAParameters GetRsaParameters(string key)
return GetRsaParameters(System.Convert.FromBase64String(key), isPrivate);
#endif
}
+#endif
#if !NET5_0_OR_GREATER
// Derived from: https://stackoverflow.com/a/32243171/, https://stackoverflow.com/a/26978561/, http://luca.ntop.org/Teaching/Appunti/asn1.html
@@ -351,7 +400,7 @@ public static bool TryParseRedirectionHeader(string redirectUrl, string initialU
var uri = new Uri(redirectUrl);
host = uri.Host;
if (string.IsNullOrEmpty(host)) return false;
- if (host.StartsWith('[') && host.EndsWith("]", StringComparison.InvariantCulture)) host = host.Substring(1, host.Length - 2);
+ if (host.StartsWith('[') && host.EndsWith("]", StringComparison.Ordinal)) host = host.Substring(1, host.Length - 2);
port = uri.Port;
user = Uri.UnescapeDataString(uri.UserInfo.Split(':')[0]);
@@ -361,7 +410,7 @@ public static bool TryParseRedirectionHeader(string redirectUrl, string initialU
var q = uri.Query.Substring(1);
foreach (var token in q.Split('&'))
{
- if (token.StartsWith("user=", StringComparison.InvariantCulture))
+ if (token.StartsWith("user=", StringComparison.Ordinal))
{
user = Uri.UnescapeDataString(token.Substring(5));
}
diff --git a/src/SingleStoreConnector/docs/README.md b/src/SingleStoreConnector/docs/README.md
index 17fcd8738..f978cfcbd 100644
--- a/src/SingleStoreConnector/docs/README.md
+++ b/src/SingleStoreConnector/docs/README.md
@@ -15,16 +15,16 @@ var builder = new SingleStoreConnectionStringBuilder
};
// open a connection asynchronously
-using var connection = new SingleStoreConnection(builder.ConnectionString);
+await using var connection = new SingleStoreConnection(builder.ConnectionString);
await connection.OpenAsync();
// create a DB command and set the SQL statement with parameters
-using var command = connection.CreateCommand();
+await using var command = connection.CreateCommand();
command.CommandText = @"SELECT * FROM orders WHERE order_id = @OrderId;";
command.Parameters.AddWithValue("@OrderId", orderId);
// execute the command and read the results
-using var reader = await command.ExecuteReaderAsync();
+await using var reader = await command.ExecuteReaderAsync();
while (reader.Read())
{
var id = reader.GetInt32("order_id");
diff --git a/tests/Conformance.Tests/Conformance.Tests.csproj b/tests/Conformance.Tests/Conformance.Tests.csproj
index ca4f8001c..2e53564af 100644
--- a/tests/Conformance.Tests/Conformance.Tests.csproj
+++ b/tests/Conformance.Tests/Conformance.Tests.csproj
@@ -1,13 +1,12 @@
- net9.0
+ net10.0
0.1.0
true
true
..\..\SingleStoreConnector.snk
true
- 11.0
diff --git a/tests/Conformance.Tests/ConnectionStringBuilderTests.cs b/tests/Conformance.Tests/ConnectionStringBuilderTests.cs
index 51b026177..5aaf6ef2a 100644
--- a/tests/Conformance.Tests/ConnectionStringBuilderTests.cs
+++ b/tests/Conformance.Tests/ConnectionStringBuilderTests.cs
@@ -9,4 +9,3 @@ public ConnectionStringBuilderTests(DbFactoryFixture fixture)
{
}
}
-
diff --git a/tests/Conformance.Tests/DataReaderTests.cs b/tests/Conformance.Tests/DataReaderTests.cs
index 41bcbdad3..4618d5f35 100644
--- a/tests/Conformance.Tests/DataReaderTests.cs
+++ b/tests/Conformance.Tests/DataReaderTests.cs
@@ -11,5 +11,7 @@ public DataReaderTests(SelectValueFixture fixture)
}
[Fact(Skip = "Deliberately throws InvalidCastException")]
- public override void GetTextReader_returns_empty_for_null_String() { }
+ public override void GetTextReader_returns_empty_for_null_String()
+ {
+ }
}
diff --git a/tests/Conformance.Tests/GetValueConversionTests.cs b/tests/Conformance.Tests/GetValueConversionTests.cs
index 71bed9f96..0b68c9106 100644
--- a/tests/Conformance.Tests/GetValueConversionTests.cs
+++ b/tests/Conformance.Tests/GetValueConversionTests.cs
@@ -21,7 +21,6 @@ public GetValueConversionTests(SelectValueFixture fixture)
public override void GetValue_for_Boolean() => TestGetValue(DbType.Boolean, ValueKind.One, (sbyte)1);
public override void GetFieldType_for_Boolean() => TestGetFieldType(DbType.Boolean, ValueKind.One, typeof(sbyte));
-
// GetBoolean allows conversions from any integral type and decimal for backwards compatibility
public override void GetBoolean_throws_for_maximum_Byte() => TestGetValue(DbType.Byte, ValueKind.Maximum, x => x.GetBoolean(0), true);
public override void GetBoolean_throws_for_maximum_Byte_with_GetFieldValue() => TestGetValue(DbType.Byte, ValueKind.Maximum, x => x.GetFieldValue(0), true);
@@ -280,7 +279,6 @@ public GetValueConversionTests(SelectValueFixture fixture)
public override void GetByte_throws_for_zero_Decimal_with_GetFieldValue() => TestGetValue(DbType.Decimal, ValueKind.Zero, x => x.GetFieldValue(0), (byte) 0);
public override Task GetByte_throws_for_zero_Decimal_with_GetFieldValueAsync() => TestGetValueAsync(DbType.Decimal, ValueKind.Zero, x => x.GetFieldValueAsync(0), (byte) 0);
-
// the minimum date permitted by MySQL is 1000-01-01; override the minimum value for DateTime tests
public override void GetDateTime_for_minimum_Date() => TestGetValue(DbType.Date, ValueKind.Minimum, x => x.GetDateTime(0), new DateTime(1000, 1, 1));
public override void GetDateTime_for_minimum_Date_with_GetFieldValue() => TestGetValue(DbType.Date, ValueKind.Minimum, x => x.GetFieldValue(0), new DateTime(1000, 1, 1));
diff --git a/tests/Conformance.Tests/TransactionTests.cs b/tests/Conformance.Tests/TransactionTests.cs
index 4a2617b57..0382bfd62 100644
--- a/tests/Conformance.Tests/TransactionTests.cs
+++ b/tests/Conformance.Tests/TransactionTests.cs
@@ -11,23 +11,37 @@ public TransactionTests(DbFactoryFixture fixture)
}
[Fact(Skip = "Deliberately throws System.NotSupportedException : IsolationLevel.Serializable is not supported.")]
- public override void BeginTransaction_works() {}
+ public override void BeginTransaction_works()
+ {
+ }
[Fact(Skip = "Deliberately throws System.NotSupportedException : IsolationLevel.Serializable is not supported.")]
- public override void Commit_transaction_clears_Connection() {}
+ public override void Commit_transaction_clears_Connection()
+ {
+ }
[Fact(Skip = "Deliberately throws System.NotSupportedException : IsolationLevel.Serializable is not supported.")]
- public override void Commit_transaction_then_Rollback_throws() {}
+ public override void Commit_transaction_then_Rollback_throws()
+ {
+ }
[Fact(Skip = "Deliberately throws System.NotSupportedException : IsolationLevel.Serializable is not supported.")]
- public override void Commit_transaction_twice_throws() {}
+ public override void Commit_transaction_twice_throws()
+ {
+ }
[Fact(Skip = "Deliberately throws System.NotSupportedException : IsolationLevel.Serializable is not supported.")]
- public override void Rollback_transaction_clears_Connection() {}
+ public override void Rollback_transaction_clears_Connection()
+ {
+ }
[Fact(Skip = "Deliberately throws System.NotSupportedException : IsolationLevel.Serializable is not supported.")]
- public override void Rollback_transaction_then_Commit_throws() {}
+ public override void Rollback_transaction_then_Commit_throws()
+ {
+ }
[Fact(Skip = "Deliberately throws System.NotSupportedException : IsolationLevel.Serializable is not supported.")]
- public override void Rollback_transaction_twice_throws() {}
+ public override void Rollback_transaction_twice_throws()
+ {
+ }
}
diff --git a/tests/SideBySide/ActivityTests.cs b/tests/SideBySide/ActivityTests.cs
index 3d3557c20..59aa2f122 100644
--- a/tests/SideBySide/ActivityTests.cs
+++ b/tests/SideBySide/ActivityTests.cs
@@ -140,6 +140,48 @@ public void SelectTags()
AssertTag(activity.Tags, "db.statement", "SELECT 1;");
}
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void ReadResultSetHeaderEvent(bool enableEvent)
+ {
+ var dataSourceBuilder = new SingleStoreDataSourceBuilder(AppConfig.ConnectionString)
+ .ConfigureTracing(o => o.EnableResultSetHeaderEvent(enableEvent));
+ using var dataSource = dataSourceBuilder.Build();
+ using var connection = dataSource.OpenConnection();
+
+ using var parentActivity = new Activity(nameof(ReadResultSetHeaderEvent));
+ parentActivity.Start();
+
+ Activity activity = null;
+ using var listener = new ActivityListener
+ {
+ ShouldListenTo = x => x.Name == "SingleStoreConnector",
+ Sample = (ref ActivityCreationOptions options) =>
+ options.TraceId == parentActivity.TraceId ? ActivitySamplingResult.AllData : ActivitySamplingResult.None,
+ ActivityStopped = x => activity = x,
+ };
+ ActivitySource.AddActivityListener(listener);
+
+ using (var command = new SingleStoreCommand("SELECT 1;", connection))
+ {
+ command.ExecuteScalar();
+ }
+
+ Assert.NotNull(activity);
+ Assert.Equal(ActivityKind.Client, activity.Kind);
+ Assert.Equal("Execute", activity.OperationName);
+ if (enableEvent)
+ {
+ var activityEvent = Assert.Single(activity.Events);
+ Assert.Equal("read-result-set-header", activityEvent.Name);
+ }
+ else
+ {
+ Assert.Empty(activity.Events);
+ }
+ }
+
private void AssertTags(IEnumerable> tags, SingleStoreConnectionStringBuilder csb)
{
AssertTag(tags, "db.system", "mysql");
diff --git a/tests/SideBySide/BatchTests.cs b/tests/SideBySide/BatchTests.cs
index 557a73824..5e85d5d67 100644
--- a/tests/SideBySide/BatchTests.cs
+++ b/tests/SideBySide/BatchTests.cs
@@ -182,8 +182,8 @@ public void IgnoreCommandTransactionIgnoresDifferentTransaction()
[InlineData(";")]
[InlineData(";\n")]
[InlineData("; -- ")]
- // [InlineData(" -- ")] TODO: uncomment if DB-53659 is done
[InlineData(" # ")]
+ //// [InlineData(" -- ")] TODO: uncomment if DB-53659 is done
public void ExecuteBatch(string suffix)
{
using var connection = new SingleStoreConnection(AppConfig.ConnectionString);
@@ -343,7 +343,7 @@ public void PrepareNeedsCommandsWithText()
private static string GetIgnoreCommandTransactionConnectionString() =>
new SingleStoreConnectionStringBuilder(AppConfig.ConnectionString)
{
- IgnoreCommandTransaction = true
+ IgnoreCommandTransaction = true,
}.ConnectionString;
}
#endif
diff --git a/tests/SideBySide/BulkLoaderAsync.cs b/tests/SideBySide/BulkLoaderAsync.cs
index 007face24..a4c6e436d 100644
--- a/tests/SideBySide/BulkLoaderAsync.cs
+++ b/tests/SideBySide/BulkLoaderAsync.cs
@@ -161,7 +161,7 @@ public async Task BulkLoadLocalCsvFileNotFound()
await connection.OpenAsync();
SingleStoreBulkLoader bl = new SingleStoreBulkLoader(connection)
{
- Timeout = 3, //Set a short timeout for this test because the file not found exception takes a long time otherwise, the timeout does not change the result
+ Timeout = 3, // Set a short timeout for this test because the file not found exception takes a long time otherwise, the timeout does not change the result
FileName = AppConfig.SingleStoreBulkLoaderLocalCsvFile + "-junk",
TableName = m_testTable,
CharacterSet = "UTF8",
@@ -198,7 +198,7 @@ public async Task BulkLoadLocalCsvFileNotFound()
}
catch (Exception exception)
{
- //We know that the exception is not a SingleStoreException, just use the assertion to fail the test
+ // We know that the exception is not a SingleStoreException, just use the assertion to fail the test
Assert.IsType(exception);
}
}
@@ -341,7 +341,7 @@ public async Task BulkLoadFileStreamInvalidOperation()
#if !BASELINE
await Assert.ThrowsAsync(async () => { var rowCount = await bl.LoadAsync(); });
#else
- await Assert.ThrowsAsync(async () => { var rowCount = await bl.LoadAsync(fileStream); });
+ await Assert.ThrowsAsync(async () => { var rowCount = await bl.LoadAsync(fileStream); });
#endif
}
@@ -649,11 +649,129 @@ public async Task BulkCopyNullDataReader()
var bulkCopy = new SingleStoreBulkCopy(connection);
await Assert.ThrowsAsync(async () => await bulkCopy.WriteToServerAsync(default(DbDataReader)));
}
+
+ [Fact]
+ public async Task BulkCopyGeographyAsync()
+ {
+ using var connection = new SingleStoreConnection(GetLocalConnectionString());
+ await connection.OpenAsync();
+
+ var dataTable = new DataTable()
+ {
+ Columns =
+ {
+ new DataColumn("geo_data", typeof(SingleStoreGeography)),
+ },
+ Rows =
+ {
+ new object[] { new SingleStoreGeography("LINESTRING(0.00000000 0.00000000, 1.00000000 1.00000000, 2.00000000 2.00000000)") },
+ new object[] { new SingleStoreGeography("POLYGON((0.00000000 0.00000000, 1.00000000 0.00000000, 1.00000000 1.00000000, 0.00000000 1.00000000, 0.00000000 0.00000000))") },
+ },
+ };
+
+ using (var cmd = new SingleStoreCommand(@"drop table if exists bulk_load_data_table;
+create rowstore table bulk_load_data_table(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ geo_data GEOGRAPHY NOT NULL
+);", connection))
+ {
+ await cmd.ExecuteNonQueryAsync();
+ }
+
+ var bc = new SingleStoreBulkCopy(connection)
+ {
+ DestinationTableName = "bulk_load_data_table",
+ ColumnMappings =
+ {
+ new()
+ {
+ SourceOrdinal = 0,
+ DestinationColumn = "geo_data",
+ },
+ },
+ };
+
+ var result = await bc.WriteToServerAsync(dataTable);
+ Assert.Equal(2, result.RowsInserted);
+ Assert.Empty(result.Warnings);
+
+ using (var cmd = new SingleStoreCommand("select geo_data from bulk_load_data_table order by id;", connection))
+ using (var reader = await cmd.ExecuteReaderAsync())
+ {
+ Assert.True(await reader.ReadAsync());
+ Assert.Equal("LINESTRING(0.00000000 0.00000000, 1.00000000 1.00000000, 2.00000000 2.00000000)", reader.GetString(0));
+ Assert.True(await reader.ReadAsync());
+ Assert.Equal("POLYGON((0.00000000 0.00000000, 1.00000000 0.00000000, 1.00000000 1.00000000, 0.00000000 1.00000000, 0.00000000 0.00000000))", reader.GetString(0));
+ Assert.False(await reader.ReadAsync());
+ }
+ }
+
+ [Fact]
+ public async Task BulkCopyGeographyPointAsync()
+ {
+ using var connection = new SingleStoreConnection(GetLocalConnectionString());
+ await connection.OpenAsync();
+
+ var dataTable = new DataTable()
+ {
+ Columns =
+ {
+ new DataColumn("point_data", typeof(SingleStoreGeographyPoint)),
+ },
+ Rows =
+ {
+ new object[] { new SingleStoreGeographyPoint("POINT(0.00000000 0.00000000)") },
+ new object[] { new SingleStoreGeographyPoint("POINT(1.00000000 1.00000000)") },
+ },
+ };
+
+ using (var cmd = new SingleStoreCommand(@"drop table if exists bulk_load_data_table;
+create table bulk_load_data_table(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ point_data GEOGRAPHYPOINT NOT NULL
+);", connection))
+ {
+ await cmd.ExecuteNonQueryAsync();
+ }
+
+ var bc = new SingleStoreBulkCopy(connection)
+ {
+ DestinationTableName = "bulk_load_data_table",
+ ColumnMappings =
+ {
+ new()
+ {
+ SourceOrdinal = 0,
+ DestinationColumn = "point_data",
+ },
+ },
+ };
+
+ var result = await bc.WriteToServerAsync(dataTable);
+ Assert.Equal(2, result.RowsInserted);
+ Assert.Empty(result.Warnings);
+
+ using (var cmd = new SingleStoreCommand(
+ "select GEOGRAPHY_LONGITUDE(point_data), GEOGRAPHY_LATITUDE(point_data) from bulk_load_data_table order by id;",
+ connection))
+ using (var reader = await cmd.ExecuteReaderAsync())
+ {
+ Assert.True(await reader.ReadAsync());
+ Assert.InRange(reader.GetDouble(0), -1e-6, 1e-6);
+ Assert.InRange(reader.GetDouble(1), -1e-6, 1e-6);
+
+ Assert.True(await reader.ReadAsync());
+ Assert.InRange(reader.GetDouble(0), 1.0 - 1e-6, 1.0 + 1e-6);
+ Assert.InRange(reader.GetDouble(1), 1.0 - 1e-6, 1.0 + 1e-6);
+
+ Assert.False(await reader.ReadAsync());
+ }
+ }
#endif
private static string GetConnectionString() => BulkLoaderSync.GetConnectionString();
private static string GetLocalConnectionString() => BulkLoaderSync.GetLocalConnectionString();
- readonly string m_testTable;
- readonly byte[] m_memoryStreamBytes;
+ private readonly string m_testTable;
+ private readonly byte[] m_memoryStreamBytes;
}
diff --git a/tests/SideBySide/BulkLoaderSync.cs b/tests/SideBySide/BulkLoaderSync.cs
index 7f6c2cd80..79e499263 100644
--- a/tests/SideBySide/BulkLoaderSync.cs
+++ b/tests/SideBySide/BulkLoaderSync.cs
@@ -1,3 +1,4 @@
+using System.Runtime.InteropServices;
using SingleStoreConnector.Core;
using Xunit.Sdk;
@@ -180,7 +181,7 @@ public void BulkLoadLocalCsvFileNotFound()
connection.Open();
SingleStoreBulkLoader bl = new SingleStoreBulkLoader(connection)
{
- Timeout = 3, //Set a short timeout for this test because the file not found exception takes a long time otherwise, the timeout does not change the result
+ Timeout = 3, // Set a short timeout for this test because the file not found exception takes a long time otherwise, the timeout does not change the result
FileName = AppConfig.SingleStoreBulkLoaderLocalCsvFile + "-junk",
TableName = m_testTable,
CharacterSet = "UTF8",
@@ -217,7 +218,7 @@ public void BulkLoadLocalCsvFileNotFound()
}
catch (Exception exception)
{
- //We know that the exception is not a SingleStoreException, just use the assertion to fail the test
+ // We know that the exception is not a SingleStoreException, just use the assertion to fail the test
Assert.IsType(exception);
}
}
@@ -469,7 +470,7 @@ public void BulkLoadMemoryStreamInvalidOperation()
#if !BASELINE
Assert.Throws(() => bl.Load());
#else
- Assert.Throws(() => bl.Load(memoryStream));
+ Assert.Throws(() => bl.Load(memoryStream));
#endif
}
@@ -673,7 +674,6 @@ public void BulkCopyDataTableWithTimeOnly()
}
#endif
-
public static IEnumerable GetBulkCopyData() =>
new object[][]
{
@@ -1066,7 +1066,7 @@ public void BulkCopyColumnMappings()
new object[] { 1, 100, "a", "A", new byte[] { 0x33, 0x30 } },
new object[] { 2, 200, "bb", "BB", new byte[] { 0x33, 0x31 } },
new object[] { 3, 300, "ccc", "CCC", new byte[] { 0x33, 0x32 } },
- }
+ },
};
var result = bulkCopy.WriteToServer(dataTable);
@@ -1120,7 +1120,7 @@ public void BulkCopyColumnMappingsInvalidSourceOrdinal()
new object[] { 1 },
new object[] { 2 },
new object[] { 3 },
- }
+ },
};
Assert.Throws(() => bulkCopy.WriteToServer(dataTable));
@@ -1157,12 +1157,72 @@ public void BulkCopyColumnMappingsInvalidDestinationColumn()
new object[] { 1 },
new object[] { 2 },
new object[] { 3 },
- }
+ },
};
Assert.Throws(() => bulkCopy.WriteToServer(dataTable));
}
+ [Fact]
+ public void BulkCopyToTableWithYear()
+ {
+ using var connection = new SingleStoreConnection(GetLocalConnectionString());
+ connection.Open();
+
+ using var cmd = connection.CreateCommand();
+ cmd.CommandText = """
+ DROP TABLE IF EXISTS bulk_copy_year;
+ CREATE TABLE bulk_copy_year(int_value int NULL, year_value year NULL)
+ """;
+ cmd.ExecuteNonQuery();
+
+ var bulkCopy = new SingleStoreBulkCopy(connection)
+ {
+ DestinationTableName = "bulk_copy_year",
+ ColumnMappings =
+ {
+ new SingleStoreBulkCopyColumnMapping(0, "int_value", null),
+ },
+ };
+
+ var dt = new DataTable();
+ dt.Columns.Add("numbers");
+ dt.Rows.Add(1);
+ dt.Rows.Add(2);
+
+ var result = bulkCopy.WriteToServer(dt);
+ Assert.Equal(2, result.RowsInserted);
+ Assert.Empty(result.Warnings);
+ }
+
+ [Fact]
+ public void BulkCopyToTableWithYearNotSupported()
+ {
+ using var connection = new SingleStoreConnection(GetLocalConnectionString());
+ connection.Open();
+
+ using var cmd = connection.CreateCommand();
+ cmd.CommandText = """
+ DROP TABLE IF EXISTS bulk_copy_year;
+ CREATE TABLE bulk_copy_year(int_value int NULL, year_value year NULL)
+ """;
+ cmd.ExecuteNonQuery();
+
+ var bulkCopy = new SingleStoreBulkCopy(connection)
+ {
+ DestinationTableName = "bulk_copy_year",
+ };
+
+ var dt = new DataTable();
+ dt.Columns.Add("numbers");
+ dt.Columns.Add("year");
+ dt.Rows.Add(1, 2000);
+ dt.Rows.Add(2, 2001);
+
+ var exception = Assert.Throws(() => bulkCopy.WriteToServer(dt));
+ Assert.Equal("'YEAR' columns are not supported by SingleStoreBulkCopy.", exception.Message);
+ }
+
[Fact]
public void BulkCopyDoesNotInsertAllRows()
{
@@ -1174,7 +1234,7 @@ public void BulkCopyDoesNotInsertAllRows()
var bcp = new SingleStoreBulkCopy(connection)
{
- DestinationTableName = "bulk_copy_duplicate_pk"
+ DestinationTableName = "bulk_copy_duplicate_pk",
};
var dataTable = new DataTable()
@@ -1189,7 +1249,7 @@ public void BulkCopyDoesNotInsertAllRows()
new object[] { 1, "a" },
new object[] { 1, "b" },
new object[] { 3, "c" },
- }
+ },
};
var ex = Assert.Throws(() => bcp.WriteToServer(dataTable));
@@ -1310,6 +1370,124 @@ public void BulkCopyDataTableConflictOption(SingleStoreBulkLoaderConflictOption
using (var cmd = new SingleStoreCommand("select b from bulk_load_data_table;", connection))
Assert.Equal(expected, cmd.ExecuteScalar());
}
+
+ [Fact]
+ public void BulkCopyGeography()
+ {
+ using var connection = new SingleStoreConnection(GetLocalConnectionString());
+ connection.Open();
+
+ var dataTable = new DataTable()
+ {
+ Columns =
+ {
+ new DataColumn("geo_data", typeof(SingleStoreGeography)),
+ },
+ Rows =
+ {
+ new object[] { new SingleStoreGeography("LINESTRING(0.00000000 0.00000000, 1.00000000 1.00000000, 2.00000000 2.00000000)") },
+ new object[] { new SingleStoreGeography("POLYGON((0.00000000 0.00000000, 1.00000000 0.00000000, 1.00000000 1.00000000, 0.00000000 1.00000000, 0.00000000 0.00000000))") },
+ },
+ };
+
+ using (var cmd = new SingleStoreCommand(@"drop table if exists bulk_load_data_table;
+create rowstore table bulk_load_data_table(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ geo_data GEOGRAPHY NOT NULL
+);", connection))
+ {
+ cmd.ExecuteNonQuery();
+ }
+
+ var bc = new SingleStoreBulkCopy(connection)
+ {
+ DestinationTableName = "bulk_load_data_table",
+ ColumnMappings =
+ {
+ new()
+ {
+ SourceOrdinal = 0,
+ DestinationColumn = "geo_data",
+ },
+ },
+ };
+
+ var result = bc.WriteToServer(dataTable);
+ Assert.Equal(2, result.RowsInserted);
+ Assert.Empty(result.Warnings);
+
+ using (var cmd = new SingleStoreCommand("select geo_data from bulk_load_data_table order by id;", connection))
+ using (var reader = cmd.ExecuteReader())
+ {
+ Assert.True(reader.Read());
+ Assert.Equal("LINESTRING(0.00000000 0.00000000, 1.00000000 1.00000000, 2.00000000 2.00000000)", reader.GetString(0));
+ Assert.True(reader.Read());
+ Assert.Equal("POLYGON((0.00000000 0.00000000, 1.00000000 0.00000000, 1.00000000 1.00000000, 0.00000000 1.00000000, 0.00000000 0.00000000))", reader.GetString(0));
+ Assert.False(reader.Read());
+ }
+ }
+
+ [Fact]
+ public void BulkCopyGeographyPoint()
+ {
+ using var connection = new SingleStoreConnection(GetLocalConnectionString());
+ connection.Open();
+
+ var dataTable = new DataTable()
+ {
+ Columns =
+ {
+ new DataColumn("point_data", typeof(SingleStoreGeographyPoint)),
+ },
+ Rows =
+ {
+ new object[] { new SingleStoreGeographyPoint("POINT(0.00000000 0.00000000)") },
+ new object[] { new SingleStoreGeographyPoint("POINT(1.00000000 1.00000000)") },
+ },
+ };
+
+ using (var cmd = new SingleStoreCommand(@"drop table if exists bulk_load_data_table;
+create table bulk_load_data_table(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ point_data GEOGRAPHYPOINT NOT NULL
+);", connection))
+ {
+ cmd.ExecuteNonQuery();
+ }
+
+ var bc = new SingleStoreBulkCopy(connection)
+ {
+ DestinationTableName = "bulk_load_data_table",
+ ColumnMappings =
+ {
+ new()
+ {
+ SourceOrdinal = 0,
+ DestinationColumn = "point_data",
+ },
+ },
+ };
+
+ var result = bc.WriteToServer(dataTable);
+ Assert.Equal(2, result.RowsInserted);
+ Assert.Empty(result.Warnings);
+
+ using (var cmd = new SingleStoreCommand(
+ "select GEOGRAPHY_LONGITUDE(point_data), GEOGRAPHY_LATITUDE(point_data) from bulk_load_data_table order by id;",
+ connection))
+ using (var reader = cmd.ExecuteReader())
+ {
+ Assert.True(reader.Read());
+ Assert.InRange(reader.GetDouble(0), -1e-6, 1e-6);
+ Assert.InRange(reader.GetDouble(1), -1e-6, 1e-6);
+
+ Assert.True(reader.Read());
+ Assert.InRange(reader.GetDouble(0), 1.0 - 1e-6, 1.0 + 1e-6);
+ Assert.InRange(reader.GetDouble(1), 1.0 - 1e-6, 1.0 + 1e-6);
+
+ Assert.False(reader.Read());
+ }
+ }
#endif
internal static string GetConnectionString() => AppConfig.ConnectionString;
@@ -1321,6 +1499,6 @@ internal static string GetLocalConnectionString()
return csb.ConnectionString;
}
- readonly string m_testTable;
- readonly byte[] m_memoryStreamBytes;
+ private readonly string m_testTable;
+ private readonly byte[] m_memoryStreamBytes;
}
diff --git a/tests/SideBySide/CancelTests.cs b/tests/SideBySide/CancelTests.cs
index 3faf8e2f4..0987d6312 100644
--- a/tests/SideBySide/CancelTests.cs
+++ b/tests/SideBySide/CancelTests.cs
@@ -44,7 +44,6 @@ public void CancelCommand()
#pragma warning restore xUnit1031 // Do not use blocking task operations in test method
}
-
[SkippableFact(ServerFeatures.Timeout)]
public void CancelReaderAsynchronously()
{
@@ -100,8 +99,7 @@ public async Task CancelCommandWithPasswordCallback()
var stopwatch = Stopwatch.StartNew();
await TestUtilities.AssertExecuteScalarReturnsOneOrIsCanceledAsync(command);
- // Assert.InRange(stopwatch.ElapsedMilliseconds, 250, 2500); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
-
+ //// Assert.InRange(stopwatch.ElapsedMilliseconds, 250, 2500); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
task.Wait(); // shouldn't throw
}
@@ -120,7 +118,7 @@ public async Task CancelCommandCancellationTokenWithPasswordCallback()
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(500));
var stopwatch = Stopwatch.StartNew();
await TestUtilities.AssertExecuteScalarReturnsOneOrIsCanceledAsync(command, cts.Token);
- // Assert.InRange(stopwatch.ElapsedMilliseconds, 250, 2500); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
+ //// Assert.InRange(stopwatch.ElapsedMilliseconds, 250, 2500); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
}
#endif
@@ -143,7 +141,7 @@ public void CancelCommandBeforeRead()
Assert.Equal((int) SingleStoreErrorCode.QueryInterrupted, ex.Number);
}
Assert.False(reader.NextResult());
- // TestUtilities.AssertDuration(stopwatch, 0, 1000); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
+ //// TestUtilities.AssertDuration(stopwatch, 0, 1000); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
Assert.InRange(rows, 0, 10000000);
}
@@ -197,7 +195,7 @@ public void DapperQueryMultiple()
stopwatch = Stopwatch.StartNew();
}
stopwatch.Stop();
- // TestUtilities.AssertDuration(stopwatch, 0, 1000); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
+ //// TestUtilities.AssertDuration(stopwatch, 0, 1000); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
}
#if !BASELINE
@@ -354,8 +352,8 @@ public async Task CancelSlowQueryWithTokenAfterExecuteReader()
// the call to ExecuteReader should block until the token is cancelled
var stopwatch = Stopwatch.StartNew();
using var reader = await cmd.ExecuteReaderAsync(cts.Token);
- // TestUtilities.AssertDuration(stopwatch, 450, 3000); commented out due to flakiness — execution can complete too quickly depending on system/load.
+ // TestUtilities.AssertDuration(stopwatch, 450, 3000); commented out due to flakiness — execution can complete too quickly depending on system/load.
var rows = 0;
try
{
@@ -367,7 +365,7 @@ public async Task CancelSlowQueryWithTokenAfterExecuteReader()
catch (OperationCanceledException ex)
{
Assert.Equal(cts.Token, ex.CancellationToken);
- // Assert.InRange(rows, 0, 100); commented out due to flakiness — we can't guarantee that it won't read more rows
+ //// Assert.InRange(rows, 0, 100); commented out due to flakiness — we can't guarantee that it won't read more rows
}
}
@@ -376,7 +374,7 @@ public async Task CancelSlowQueryWithTokenAfterNextResult()
{
using var cmd = new SingleStoreCommand("SELECT 1; " + c_slowQuery, m_database.Connection)
{
- CommandTimeout = 0
+ CommandTimeout = 0,
};
using var reader = await cmd.ExecuteReaderAsync();
@@ -390,8 +388,8 @@ public async Task CancelSlowQueryWithTokenAfterNextResult()
// the call to NextResult should block until the token is cancelled
var stopwatch = Stopwatch.StartNew();
Assert.True(await reader.NextResultAsync(cts.Token));
- // TestUtilities.AssertDuration(stopwatch, 450, 1500); commented out due to flakiness — execution can complete too quickly depending on system/load.
+ // TestUtilities.AssertDuration(stopwatch, 450, 1500); commented out due to flakiness — execution can complete too quickly depending on system/load.
int rows = 0;
try
{
@@ -402,7 +400,7 @@ public async Task CancelSlowQueryWithTokenAfterNextResult()
catch (OperationCanceledException ex)
{
Assert.Equal(cts.Token, ex.CancellationToken);
- // Assert.InRange(rows, 0, 100); commented out due to flakiness — we can't guarantee that it won't read more rows
+ //// Assert.InRange(rows, 0, 100); commented out due to flakiness — we can't guarantee that it won't read more rows
}
}
@@ -454,9 +452,7 @@ public void CancelBatchCommand()
var stopwatch = Stopwatch.StartNew();
var ex = Assert.Throws(() => batch.ExecuteScalar());
Assert.Equal("Query execution was interrupted", ex.Message);
-
- // Assert.InRange(stopwatch.ElapsedMilliseconds, 250, 2500); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
-
+ //// Assert.InRange(stopwatch.ElapsedMilliseconds, 250, 2500); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
task.Wait(); // shouldn't throw
}
@@ -525,7 +521,7 @@ public void CancelBatchBeforeRead()
Assert.Equal(SingleStoreErrorCode.QueryInterrupted, ex.ErrorCode);
}
Assert.False(reader.NextResult());
- // TestUtilities.AssertDuration(stopwatch, 0, 1000); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
+ //// TestUtilities.AssertDuration(stopwatch, 0, 1000); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
Assert.InRange(rows, 0, 10000000);
}
@@ -669,8 +665,7 @@ public async Task CancelSlowQueryBatchWithTokenAfterExecuteReader()
// the call to ExecuteReader should block until the token is cancelled
var stopwatch = Stopwatch.StartNew();
using var reader = await batch.ExecuteReaderAsync(cts.Token);
- // TestUtilities.AssertDuration(stopwatch, 450, 3000); commented out due to flakiness — execution can complete too quickly depending on system/load.
-
+ //// TestUtilities.AssertDuration(stopwatch, 450, 3000); commented out due to flakiness — execution can complete too quickly depending on system/load.
var rows = 0;
try
{
@@ -682,7 +677,7 @@ public async Task CancelSlowQueryBatchWithTokenAfterExecuteReader()
catch (OperationCanceledException ex)
{
Assert.Equal(cts.Token, ex.CancellationToken);
- // Assert.InRange(rows, 0, 100); commented out due to flakiness — we can't guarantee that it won't read more rows
+ //// Assert.InRange(rows, 0, 100); commented out due to flakiness — we can't guarantee that it won't read more rows
}
}
@@ -709,8 +704,7 @@ public async Task CancelSlowQueryBatchWithTokenAfterNextResult()
// the call to NextResult should block until the token is cancelled
var stopwatch = Stopwatch.StartNew();
Assert.True(await reader.NextResultAsync(cts.Token));
- // TestUtilities.AssertDuration(stopwatch, 450, 1500); commented out due to flakiness — execution can complete too quickly depending on system/load.
-
+ //// TestUtilities.AssertDuration(stopwatch, 450, 1500); commented out due to flakiness — execution can complete too quickly depending on system/load.
int rows = 0;
try
{
@@ -721,12 +715,13 @@ public async Task CancelSlowQueryBatchWithTokenAfterNextResult()
catch (OperationCanceledException ex)
{
Assert.Equal(cts.Token, ex.CancellationToken);
- // Assert.InRange(rows, 0, 100); commented out due to flakiness — we can't guarantee that it won't read more rows
+ //// Assert.InRange(rows, 0, 100); commented out due to flakiness — we can't guarantee that it won't read more rows
}
}
Assert.False(await reader.NextResultAsync());
}
+#endif
#endif
private static CancellationToken GetCanceledToken()
@@ -736,15 +731,14 @@ private static CancellationToken GetCanceledToken()
return cts.Token;
}
- static readonly CancellationToken s_canceledToken = GetCanceledToken();
-#endif
-
// returns billions of rows
- const string c_hugeQuery = @"select * from integers a join integers b join integers c join integers d join integers e join integers f join integers g join integers h;";
+ private const string c_hugeQuery = @"select * from integers a join integers b join integers c join integers d join integers e join integers f join integers g join integers h;";
// takes a long time to return any rows
- const string c_slowQuery = @"select * from integers a join integers b join integers c join integers d join integers e join integers f join integers g join integers h
+ private const string c_slowQuery = @"select * from integers a join integers b join integers c join integers d join integers e join integers f join integers g join integers h
where sqrt(a.value) + sqrt(b.value) + sqrt(c.value) + sqrt(d.value) + sqrt(e.value) + sqrt(f.value) + sqrt(g.value) + sqrt(h.value) = 20;";
- readonly DatabaseFixture m_database;
+ private static readonly CancellationToken s_canceledToken = GetCanceledToken();
+
+ private readonly DatabaseFixture m_database;
}
diff --git a/tests/SideBySide/CharacterSetTests.cs b/tests/SideBySide/CharacterSetTests.cs
index 17dbbdb36..cd290f9f1 100644
--- a/tests/SideBySide/CharacterSetTests.cs
+++ b/tests/SideBySide/CharacterSetTests.cs
@@ -105,5 +105,5 @@ public void CollationConnection(bool reopenConnection)
Assert.Equal(expected, collation);
}
- readonly DatabaseFixture m_database;
+ private readonly DatabaseFixture m_database;
}
diff --git a/tests/SideBySide/ChunkStream.cs b/tests/SideBySide/ChunkStream.cs
new file mode 100644
index 000000000..3fd4fda23
--- /dev/null
+++ b/tests/SideBySide/ChunkStream.cs
@@ -0,0 +1,130 @@
+namespace SideBySide;
+
+internal sealed class ChunkStream : Stream
+{
+ public ChunkStream(byte[] data, int chunkLength)
+ {
+ if (data is null)
+ throw new ArgumentNullException(nameof(data));
+ if (chunkLength <= 0)
+ throw new ArgumentOutOfRangeException(nameof(chunkLength));
+
+ m_data = data;
+ m_chunkLength = chunkLength;
+ m_position = 0;
+ }
+
+ public override bool CanRead => true;
+ public override bool CanSeek => false;
+ public override bool CanWrite => false;
+ public override long Length => m_data.Length;
+ public override long Position
+ {
+ get => m_position;
+ set => throw new NotSupportedException();
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ if (buffer is null)
+ throw new ArgumentNullException(nameof(buffer));
+ if (offset < 0 || offset > buffer.Length)
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ if (count < 0 || offset + count > buffer.Length)
+ throw new ArgumentOutOfRangeException(nameof(count));
+
+ return Read(buffer.AsSpan(offset, count));
+ }
+
+ public
+#if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER
+ override
+#endif
+ int Read(Span buffer)
+ {
+ if (m_position >= m_data.Length)
+ return 0;
+
+ // Read at most chunkLength bytes
+ var bytesToRead = Math.Min(buffer.Length, Math.Min(m_chunkLength, m_data.Length - m_position));
+
+ // Copy data from the actual data array
+ m_data.AsSpan(m_position, bytesToRead).CopyTo(buffer);
+
+ m_position += bytesToRead;
+ return bytesToRead;
+ }
+
+ public override int ReadByte()
+ {
+ Span buffer = stackalloc byte[1];
+ var bytesRead = Read(buffer);
+ return bytesRead == 0 ? -1 : buffer[0];
+ }
+
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ if (buffer is null)
+ throw new ArgumentNullException(nameof(buffer));
+ if (offset < 0 || offset > buffer.Length)
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ if (count < 0 || offset + count > buffer.Length)
+ throw new ArgumentOutOfRangeException(nameof(count));
+
+ if (cancellationToken.IsCancellationRequested)
+ return Task.FromCanceled(cancellationToken);
+
+ try
+ {
+ return Task.FromResult(Read(buffer.AsSpan(offset, count)));
+ }
+ catch (Exception ex)
+ {
+ return Task.FromException(ex);
+ }
+ }
+
+ public
+#if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER
+ override
+#endif
+ ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ return new(Task.FromCanceled(cancellationToken));
+
+ try
+ {
+ return new(Read(buffer.Span));
+ }
+ catch (Exception ex)
+ {
+ return new(Task.FromException(ex));
+ }
+ }
+
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException();
+
+ public override void WriteByte(byte value) =>
+ throw new NotSupportedException();
+
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
+ throw new NotSupportedException();
+
+ public override void SetLength(long value) =>
+ throw new NotSupportedException();
+
+ public override long Seek(long offset, SeekOrigin origin) =>
+ throw new NotSupportedException();
+
+ public override void Flush() =>
+ throw new NotSupportedException();
+
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ throw new NotSupportedException();
+
+ private readonly byte[] m_data;
+ private readonly int m_chunkLength;
+ private int m_position;
+}
diff --git a/tests/SideBySide/ClientFactoryTests.cs b/tests/SideBySide/ClientFactoryTests.cs
index 48ed0e7fb..955cd23c8 100644
--- a/tests/SideBySide/ClientFactoryTests.cs
+++ b/tests/SideBySide/ClientFactoryTests.cs
@@ -1,7 +1,3 @@
-#if BASELINE
-using SingleStoreConnectorFactory = MySql.Data.MySqlClient.MySqlClientFactory;
-#endif
-
namespace SideBySide;
public class ClientFactoryTests
@@ -24,7 +20,6 @@ public void CreateConnectionStringBuilder()
Assert.IsType(SingleStoreConnectorFactory.Instance.CreateConnectionStringBuilder());
}
-
[Fact]
public void CreateParameter()
{
diff --git a/tests/SideBySide/CommandBuilderTests.cs b/tests/SideBySide/CommandBuilderTests.cs
index 57af01f81..f74f910ec 100644
--- a/tests/SideBySide/CommandBuilderTests.cs
+++ b/tests/SideBySide/CommandBuilderTests.cs
@@ -136,11 +136,11 @@ value varchar(100)
[Theory]
[InlineData("test", "`test`")]
[InlineData("te`st", "`te``st`")]
- [InlineData("`test`", "```test```"
#if BASELINE
- , Skip = "Doesn't quote leading quotes"
+ [InlineData("`test`", "```test```", Skip = "Doesn't quote leading quotes")]
+#else
+ [InlineData("`test`", "```test```")]
#endif
- )]
public void QuoteIdentifier(string input, string expected)
{
var cb = new SingleStoreCommandBuilder();
@@ -158,5 +158,5 @@ public void UnquoteIdentifier(string input, string expected)
Assert.Equal(expected, cb.UnquoteIdentifier(input));
}
- readonly DatabaseFixture m_database;
+ private readonly DatabaseFixture m_database;
}
diff --git a/tests/SideBySide/CommandTests.cs b/tests/SideBySide/CommandTests.cs
index cda46d853..ef2ed8304 100644
--- a/tests/SideBySide/CommandTests.cs
+++ b/tests/SideBySide/CommandTests.cs
@@ -267,7 +267,7 @@ public void IgnoreCommandTransactionIgnoresNull()
{
using var connection = new SingleStoreConnection(GetIgnoreCommandTransactionConnectionString());
connection.Open();
- using var _ = connection.BeginTransaction();
+ using var ignoredTransaction = connection.BeginTransaction();
using var command = connection.CreateCommand();
command.CommandText = "SELECT 1;";
TestUtilities.AssertIsOne(command.ExecuteScalar());
@@ -469,10 +469,10 @@ private static string GetIgnoreCommandTransactionConnectionString()
#else
return new SingleStoreConnectionStringBuilder(AppConfig.ConnectionString)
{
- IgnoreCommandTransaction = true
+ IgnoreCommandTransaction = true,
}.ConnectionString;
#endif
}
- readonly DatabaseFixture m_database;
+ private readonly DatabaseFixture m_database;
}
diff --git a/tests/SideBySide/CommandTimeoutTests.cs b/tests/SideBySide/CommandTimeoutTests.cs
index 191e7c869..399ed839d 100644
--- a/tests/SideBySide/CommandTimeoutTests.cs
+++ b/tests/SideBySide/CommandTimeoutTests.cs
@@ -66,7 +66,7 @@ public void CommandTimeoutWithSleepSync()
}
#endif
sw.Stop();
- // TestUtilities.AssertDuration(sw, cmd.CommandTimeout * 1000 - 100, 500); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
+ //// TestUtilities.AssertDuration(sw, cmd.CommandTimeout * 1000 - 100, 500); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
}
Assert.Equal(connectionState, m_connection.State);
@@ -92,7 +92,7 @@ public async Task CommandTimeoutWithSleepAsync()
}
#endif
sw.Stop();
- // TestUtilities.AssertDuration(sw, cmd.CommandTimeout * 1000 - 100, 700); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
+ //// TestUtilities.AssertDuration(sw, cmd.CommandTimeout * 1000 - 100, 700); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
}
Assert.Equal(connectionState, m_connection.State);
@@ -133,7 +133,7 @@ create procedure sleep_sproc(seconds INT) as
}
#endif
sw.Stop();
- // TestUtilities.AssertDuration(sw, cmd.CommandTimeout * 1000 - 100, 500); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
+ //// TestUtilities.AssertDuration(sw, cmd.CommandTimeout * 1000 - 100, 500); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
}
[SkippableFact(ServerFeatures.Timeout)]
@@ -161,7 +161,7 @@ public void MultipleCommandTimeoutWithSleepSync()
#endif
sw.Stop();
- // TestUtilities.AssertDuration(sw, cmd.CommandTimeout * 1000 - 100, 500); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
+ //// TestUtilities.AssertDuration(sw, cmd.CommandTimeout * 1000 - 100, 500); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
}
Assert.Equal(connectionState, m_connection.State);
@@ -191,7 +191,7 @@ public async Task MultipleCommandTimeoutWithSleepAsync()
#endif
sw.Stop();
- // TestUtilities.AssertDuration(sw, cmd.CommandTimeout * 1000 - 100, 550); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
+ //// TestUtilities.AssertDuration(sw, cmd.CommandTimeout * 1000 - 100, 550); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
}
Assert.Equal(connectionState, m_connection.State);
@@ -239,7 +239,6 @@ public async Task CommandTimeoutResetsOnReadAsync()
Assert.Equal(ConnectionState.Open, m_connection.State);
}
-
[Fact]
public void TransactionCommandTimeoutWithSleepSync()
{
@@ -261,7 +260,7 @@ public void TransactionCommandTimeoutWithSleepSync()
}
#endif
sw.Stop();
- // TestUtilities.AssertDuration(sw, cmd.CommandTimeout * 1000 - 100, 500); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
+ //// TestUtilities.AssertDuration(sw, cmd.CommandTimeout * 1000 - 100, 500); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
}
Assert.Equal(connectionState, m_connection.State);
@@ -293,6 +292,6 @@ public async Task TransactionCommandTimeoutWithSleepAsync()
Assert.Equal(connectionState, m_connection.State);
}
- readonly DatabaseFixture m_database;
- readonly SingleStoreConnection m_connection;
+ private readonly DatabaseFixture m_database;
+ private readonly SingleStoreConnection m_connection;
}
diff --git a/tests/SideBySide/ConnectAsync.cs b/tests/SideBySide/ConnectAsync.cs
index f1152a010..62668898a 100644
--- a/tests/SideBySide/ConnectAsync.cs
+++ b/tests/SideBySide/ConnectAsync.cs
@@ -1,7 +1,4 @@
using System.Security.Authentication;
-#if !BASELINE
-using SingleStoreConnector.Authentication.Ed25519;
-#endif
namespace SideBySide;
@@ -128,7 +125,7 @@ public async Task ConnectTimeoutAsync()
var ex = await Assert.ThrowsAsync(connection.OpenAsync);
stopwatch.Stop();
Assert.Equal((int) SingleStoreErrorCode.UnableToConnectToHost, ex.Number);
- // TestUtilities.AssertDuration(stopwatch, 1900, 1500); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
+ //// TestUtilities.AssertDuration(stopwatch, 1900, 1500); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
}
[SkippableFact(ServerFeatures.Timeout, Baseline = "https://bugs.mysql.com/bug.php?id=94760")]
@@ -152,7 +149,7 @@ public async Task ConnectTimeoutAsyncCancellationToken()
stopwatch.Stop();
Assert.Equal(TaskStatus.Canceled, task.Status);
Assert.Equal(cts.Token, ex.CancellationToken);
- // TestUtilities.AssertDuration(stopwatch, 1900, 1500); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
+ //// TestUtilities.AssertDuration(stopwatch, 1900, 1500); commented out due to flakiness — execution can complete too quickly/slow depending on system/load.
}
#if !BASELINE
@@ -191,7 +188,11 @@ public async Task UsePasswordProviderPasswordTakesPrecedence()
await SingleStoreConnection.ClearPoolAsync(connection);
var wasCalled = false;
- connection.ProvidePasswordCallback = _ => { wasCalled = true; return password; };
+ connection.ProvidePasswordCallback = _ =>
+ {
+ wasCalled = true;
+ return password;
+ };
await connection.OpenAsync();
Assert.False(wasCalled);
@@ -431,7 +432,7 @@ public async Task CachingSha2WithoutSecureConnection()
[SkippableFact(ServerFeatures.Ed25519)]
public async Task Ed25519Authentication()
{
- Ed25519AuthenticationPlugin.Install();
+ SingleStoreConnector.Authentication.Ed25519.Ed25519AuthenticationPlugin.Install();
var csb = AppConfig.CreateConnectionStringBuilder();
csb.UserID = "ed25519user";
@@ -444,7 +445,7 @@ public async Task Ed25519Authentication()
[SkippableFact(ServerFeatures.Ed25519)]
public async Task MultiAuthentication()
{
- Ed25519AuthenticationPlugin.Install();
+ SingleStoreConnector.Authentication.Ed25519.Ed25519AuthenticationPlugin.Install();
var csb = AppConfig.CreateConnectionStringBuilder();
csb.UserID = "multiAuthUser";
csb.Password = "secret";
@@ -452,6 +453,18 @@ public async Task MultiAuthentication()
using var connection = new SingleStoreConnection(csb.ConnectionString);
await connection.OpenAsync();
}
+
+ [SkippableFact(ServerFeatures.ParsecAuthentication)]
+ public async Task Parsec()
+ {
+ SingleStoreConnector.Authentication.Ed25519.ParsecAuthenticationPlugin.Install();
+ var csb = AppConfig.CreateConnectionStringBuilder();
+ csb.UserID = "parsec-user";
+ csb.Password = "P@rs3c-Pa55";
+ csb.Database = null;
+ using var connection = new SingleStoreConnection(csb.ConnectionString);
+ await connection.OpenAsync();
+ }
#endif
// To create a MariaDB GSSAPI user for a current user
@@ -479,6 +492,7 @@ public async Task GoodServerSPN()
{
var csb = AppConfig.CreateGSSAPIConnectionStringBuilder();
string serverSPN;
+
// Use server's variable gssapi_principal_name as SPN
using (var connection = new SingleStoreConnection(csb.ConnectionString))
{
@@ -548,6 +562,5 @@ public async Task DisposeAsyncRaisesDisposed()
Assert.Equal(1, disposedCount);
}
- readonly DatabaseFixture m_database;
+ private readonly DatabaseFixture m_database;
}
-
diff --git a/tests/SideBySide/ConnectSync.cs b/tests/SideBySide/ConnectSync.cs
index 695884b81..54dfae485 100644
--- a/tests/SideBySide/ConnectSync.cs
+++ b/tests/SideBySide/ConnectSync.cs
@@ -98,7 +98,7 @@ public void NonExistentPipe()
PipeName = "nonexistingpipe",
ConnectionProtocol = SingleStoreConnectionProtocol.NamedPipe,
Server = ".",
- ConnectionTimeout = 1
+ ConnectionTimeout = 1,
};
var sw = Stopwatch.StartNew();
@@ -265,7 +265,11 @@ public void UsePasswordProviderPasswordTakesPrecedence()
SingleStoreConnection.ClearPool(connection);
var wasCalled = false;
- connection.ProvidePasswordCallback = _ => { wasCalled = true; return password; };
+ connection.ProvidePasswordCallback = _ =>
+ {
+ wasCalled = true;
+ return password;
+ };
connection.Open();
Assert.False(wasCalled);
@@ -604,5 +608,5 @@ public void DisposeRaisesDisposed()
Assert.Equal(1, disposedCount);
}
- readonly DatabaseFixture m_database;
+ private readonly DatabaseFixture m_database;
}
diff --git a/tests/SideBySide/DataAdapterTests.cs b/tests/SideBySide/DataAdapterTests.cs
index 45cace751..b8b819940 100644
--- a/tests/SideBySide/DataAdapterTests.cs
+++ b/tests/SideBySide/DataAdapterTests.cs
@@ -187,10 +187,9 @@ public void BatchUpdate()
da.Update(ds);
}
- Assert.Equal(new List{ "two", "three", "four" }, m_connection.Query("SELECT text_value FROM data_adapter ORDER BY int_value"));
+ Assert.Equal(new List { "two", "three", "four" }, m_connection.Query("SELECT text_value FROM data_adapter ORDER BY int_value"));
}
-
[Fact]
public void BatchInsert()
{
@@ -326,5 +325,5 @@ public void ConvertBatchToCommandParameters()
}
#endif
- readonly SingleStoreConnection m_connection;
+ private readonly SingleStoreConnection m_connection;
}
diff --git a/tests/SideBySide/DataTypes.cs b/tests/SideBySide/DataTypes.cs
index 35b3a34a6..9211f250e 100644
--- a/tests/SideBySide/DataTypes.cs
+++ b/tests/SideBySide/DataTypes.cs
@@ -1,4 +1,5 @@
using System.Globalization;
+using System.Runtime.InteropServices;
using SingleStoreConnector.Core;
#if BASELINE
using MySql.Data.Types;
@@ -9,15 +10,14 @@
// However, DbDataReader.GetString etc. are documented as throwing InvalidCastException: https://msdn.microsoft.com/en-us/library/system.data.common.dbdatareader.getstring.aspx
// Additionally, that is what DbDataReader.GetFieldValue throws. For consistency, we prefer InvalidCastException.
#if BASELINE
-using GetValueWhenNullException = System.Data.SqlTypes.SqlNullValueException;
-using GetGuidWhenNullException = MySql.Data.MySqlClient.SingleStoreException;
using GetBytesWhenNullException = System.NullReferenceException;
using GetGeographyWhenNullException = System.Exception;
#else
-using GetValueWhenNullException = System.InvalidCastException;
-using GetGuidWhenNullException = System.InvalidCastException;
using GetBytesWhenNullException = System.InvalidCastException;
using GetGeographyWhenNullException = System.InvalidCastException;
+using GetGuidWhenNullException = System.InvalidCastException;
+using GetStreamWhenNullException = System.InvalidCastException;
+using GetValueWhenNullException = System.InvalidCastException;
#endif
namespace SideBySide;
@@ -217,7 +217,7 @@ public void QueryTinyIntSbyte(string column, string dataTypeName, object[] expec
DoQuery("bools", column, dataTypeName, expected, reader => reader.GetSByte(0), baselineCoercedNullValue: default(sbyte), connection: connection);
}
- [Theory()]
+ [Theory]
[InlineData("TinyInt1U", "TINYINT", new object[] { null, (byte) 0, (byte) 1, (byte) 0, (byte) 1, (byte) 255, (byte) 123 })]
public void QueryTinyInt1Unsigned(string column, string dataTypeName, object[] expected)
{
@@ -313,8 +313,8 @@ public void QueryDouble(string column, string dataTypeName, object[] expected)
[Theory]
[InlineData("SmallDecimal", new object[] { null, "0", "-999.99", "-0.01", "999.99", "0.01" })]
[InlineData("MediumDecimal", new object[] { null, "0", "-999999999999.99999999", "-0.00000001", "999999999999.99999999", "0.00000001" })]
- // value exceeds the range of a decimal and cannot be deserialized
- // [InlineData("BigDecimal", new object[] { null, "0", "-99999999999999999999.999999999999999999999999999999", "-0.000000000000000000000000000001", "99999999999999999999.999999999999999999999999999999", "0.000000000000000000000000000001" })]
+ //// value exceeds the range of a decimal and cannot be deserialized
+ //// [InlineData("BigDecimal", new object[] { null, "0", "-99999999999999999999.999999999999999999999999999999", "-0.000000000000000000000000000001", "99999999999999999999.999999999999999999999999999999", "0.000000000000000000000000000001" })]
public void QueryDecimal(string column, object[] expected)
{
for (int i = 0; i < expected.Length; i++)
@@ -326,6 +326,7 @@ public void QueryDecimal(string column, object[] expected)
[Theory]
[InlineData("utf8", new[] { null, "", "ASCII", "Ũńıċōđĕ", c_251ByteString })]
[InlineData("utf8bin", new[] { null, "", "ASCII", "Ũńıċōđĕ", c_251ByteString })]
+ [InlineData("nonguid_utf8", new[] { null, "", "ASCII", "Ũńıċōđĕ", "This string has 36 characters in it." })]
public void QueryString(string column, string[] expected)
{
DoQuery("strings", column, "VARCHAR", expected, reader => reader.GetString(0));
@@ -337,7 +338,7 @@ public void QueryString(string column, string[] expected)
}, getFieldValueType: typeof(TextReader));
#endif
}
- const string c_251ByteString = "This string has exactly 251 characters in it. The encoded length is stored as 0xFC 0xFB 0x00. 0xFB (i.e., 251) is the sentinel byte indicating \"this field is null\". Incorrectly interpreting the (decoded) length as the sentinel byte would corrupt data.";
+ private const string c_251ByteString = "This string has exactly 251 characters in it. The encoded length is stored as 0xFC 0xFB 0x00. 0xFB (i.e., 251) is the sentinel byte indicating \"this field is null\". Incorrectly interpreting the (decoded) length as the sentinel byte would corrupt data.";
[Theory]
[InlineData("guid", "CHAR(36)", new object[] { null, "00000000-0000-0000-0000-000000000000", "00000000-0000-0000-c000-000000000046", "fd24a0e8-c3f2-4821-a456-35da2dc4bb8f", "6A0E0A40-6228-11D3-A996-0050041896C8" })]
@@ -444,9 +445,9 @@ public async Task QueryWithGuidParameter(bool oldGuids)
Assert.Equal(oldGuids ? 0L : 1L, (await connection.QueryAsync(@"select count(*) from datatypes_strings where guid = @guid", new { guid = new Guid("fd24a0e8-c3f2-4821-a456-35da2dc4bb8f") }).ConfigureAwait(false)).SingleOrDefault());
Assert.Equal(oldGuids ? 0L : 1L, (await connection.QueryAsync(@"select count(*) from datatypes_strings where guidbin = @guid", new { guid = new Guid("fd24a0e8-c3f2-4821-a456-35da2dc4bb8f") }).ConfigureAwait(false)).SingleOrDefault());
}
- catch (SingleStoreException ex) when (oldGuids && ex.Number is 1300 or 3854) // InvalidCharacterString, CannotConvertString
+ //// 1300 = InvalidCharacterString, 3854 = CannotConvertString.
+ catch (SingleStoreException ex) when (oldGuids && ex.Number is 1300 or 3854)
{
- // new error in MySQL 8.0.24, MariaDB 10.5
}
Assert.Equal(oldGuids ? 1L : 0L, (await connection.QueryAsync(@"select count(*) from datatypes_blobs where guidbin = @guid", new { guid = new Guid(0x33221100, 0x5544, 0x7766, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF) }).ConfigureAwait(false)).SingleOrDefault());
}
@@ -565,7 +566,7 @@ insert into guid_format(c36, c32, b16, tsb16, leb16, t, b) values(
new() { Value = isLittleEndianBinary16 ? guid : guidAsLittleEndianBinary16 },
new() { Value = guidAsChar32 },
new() { Value = isBinary16 ? guidAsBinary16 : isTimeSwapBinary16 ? guidAsTimeSwapBinary16 : guidAsLittleEndianBinary16 },
- }
+ },
};
cmd.ExecuteNonQuery();
cmd.CommandText = "select c36, c32, b16, tsb16, leb16, t, b from guid_format;";
@@ -722,7 +723,7 @@ insert into date_time_kind(d, dt0, dt6) values(?, ?, ?)", connection)
new() { Value = dateTimeIn },
new() { Value = dateTimeIn },
new() { Value = dateTimeIn },
- }
+ },
};
if (success)
{
@@ -749,11 +750,7 @@ insert into date_time_kind(d, dt0, dt6) values(?, ?, ?)", connection)
[InlineData("`Time`", "TIME", new object[] { null, "-838 -59 -59", "838 59 59", "0 0 0", "0 14 3 4 567890" })]
public void QueryTime(string column, string dataTypeName, object[] expected)
{
- DoQuery("times", column, dataTypeName, ConvertToTimeSpan(expected), reader => reader.GetTimeSpan(0)
-#if BASELINE // https://bugs.mysql.com/bug.php?id=103801
- , omitWherePrepareTest: true
-#endif
- );
+ DoQuery("times", column, dataTypeName, ConvertToTimeSpan(expected), reader => reader.GetTimeSpan(0));
}
#if NET6_0_OR_GREATER && !BASELINE
@@ -1038,8 +1035,8 @@ public void ReadVarCharAsDate(string value, bool prepare, string expectedValue)
}
[Theory]
- // [InlineData("Geography", "GEOGRAPHY", "POINT(1.00000000 1.00000000)")] those two are failing bc we use string to represent geospatial types => SS returns POINT(0.99999998 1.00000003)
- // [InlineData("Point", "POINT", "POINT(1.00000000 1.00000000)")]
+ //// [InlineData("Geography", "GEOGRAPHY", "POINT(1.00000000 1.00000000)")] those two are failing bc we use string to represent geospatial types => SS returns POINT(0.99999998 1.00000003)
+ //// [InlineData("Point", "POINT", "POINT(1.00000000 1.00000000)")]
[InlineData("LineString", "GEOGRAPHY", "LINESTRING(0.00000000 0.00000000, 1.00000000 1.00000000, 2.00000000 2.00000000)")]
[InlineData("Polygon", "GEOGRAPHY", "POLYGON((0.00000000 0.00000000, 1.00000000 0.00000000, 1.00000000 1.00000000, 0.00000000 1.00000000, 0.00000000 0.00000000))")]
public void QueryGeography(string columnName, string dataTypeName, string expectedGeography)
@@ -1058,7 +1055,7 @@ public void QueryGeography(string columnName, string dataTypeName, string expect
var geographyData = new string[]
{
null,
- expectedGeography
+ expectedGeography,
};
DoQuery("geography", columnName, dataTypeName, geographyData, reader => reader.GetString(0), connection: connectionWithParam);
@@ -1097,7 +1094,7 @@ private static object CreateGeography(string data)
private static object CreateGeographyPoint(string data)
{
- if(data is null)
+ if (data is null)
return null;
return new SingleStoreGeographyPoint(data);
@@ -1181,7 +1178,7 @@ public void GetSchemaTable(string column, string table, SingleStoreDbType mySqlD
}
DoGetSchemaTable(column, table, mySqlDbType, columnSize, dataType, flags, precision, scale, connection: connectionWithParam);
- if(connectionWithParam != null)
+ if (connectionWithParam != null)
connectionWithParam.Close();
}
@@ -1242,7 +1239,9 @@ private void DoGetSchemaTable(string column, string table, SingleStoreDbType myS
{
}
else
+ {
Assert.Equal(columnSize, schema["ColumnSize"]);
+ }
#endif
Assert.Equal(isLong, schema["IsLong"]);
Assert.Equal(isAutoIncrement, schema["IsAutoIncrement"]);
@@ -1351,6 +1350,7 @@ public void GetSchemaTableAfterNextResult()
[InlineData("utf8bin", "datatypes_strings", SingleStoreDbType.VarChar, "VARCHAR", 300, typeof(string), "N", -1, 0)]
[InlineData("guid", "datatypes_strings", SingleStoreDbType.Guid, "CHAR(36)", 36, typeof(Guid), "N", -1, 0)]
[InlineData("guidbin", "datatypes_strings", SingleStoreDbType.Guid, "CHAR(36)", 36, typeof(Guid), "N", -1, 0)]
+ [InlineData("nonguid_utf8", "datatypes_strings", SingleStoreDbType.VarChar, "VARCHAR", 36, typeof(string), "N", -1, 0)]
[InlineData("Date", "datatypes_times", SingleStoreDbType.Date, "DATE", 10, typeof(DateTime), "N", -1, 0)]
[InlineData("DateTime", "datatypes_times", SingleStoreDbType.DateTime, "DATETIME", 26, typeof(DateTime), "N", -1, 6)]
[InlineData("Timestamp", "datatypes_times", SingleStoreDbType.Timestamp, "TIMESTAMP", 26, typeof(DateTime), "N", -1, 6)]
@@ -1378,7 +1378,7 @@ public void GetColumnSchema(string column, string table, SingleStoreDbType mySql
}
DoGetColumnSchema(column, table, mySqlDbType, dataTypeName, columnSize, dataType, flags, precision, scale, connection: connectionWithParam);
- if(connectionWithParam != null)
+ if (connectionWithParam != null)
connectionWithParam.Close();
}
@@ -1412,9 +1412,9 @@ private void DoGetColumnSchema(string column, string table, SingleStoreDbType my
// the condition below accounts for wrong charset reported in SingleStore 7.5 and 7.6
// when dealing with utf8mb4 data
- if ( !( (column == "utf8" || column == "utf8bin") &&
- connection.Session.S2ServerVersion.Version.CompareTo(new Version(7,5,0)) > 0 &&
- connection.Session.S2ServerVersion.Version.CompareTo(new Version(7,8,0)) < 0 ))
+ if (!((column == "utf8" || column == "utf8bin") &&
+ connection.Session.S2ServerVersion.Version.CompareTo(new Version(7, 5, 0)) > 0 &&
+ connection.Session.S2ServerVersion.Version.CompareTo(new Version(7, 8, 0)) < 0))
{
// TODO: PLAT-6085: remove this if
if (column != "Single" && column != "Double")
@@ -1569,7 +1569,7 @@ public void StoredProcedureParameter(string column, string table, string dataTyp
[InlineData("DateTime", "datatypes_times", "DATETIME(6)")]
[InlineData("Timestamp", "datatypes_times", "TIMESTAMP(6)")]
[InlineData("Time", "datatypes_times", "TIME(6)")]
- // [InlineData("Year", "datatypes_times", "YEAR")]
+ //// [InlineData("Year", "datatypes_times", "YEAR")]
[InlineData("value", "datatypes_json_core", "JSON")]
public void BulkCopyDataReader(string column, string table, string dataTypeName)
{
@@ -1910,4 +1910,3 @@ private static int[] SplitAndParse(object obj)
private SingleStoreConnectionStringBuilder CreateConnectionStringBuilder() => new(AppConfig.ConnectionString);
}
-
diff --git a/tests/SideBySide/DataTypesFixture.cs b/tests/SideBySide/DataTypesFixture.cs
index 32928f614..c6f17bd09 100644
--- a/tests/SideBySide/DataTypesFixture.cs
+++ b/tests/SideBySide/DataTypesFixture.cs
@@ -121,21 +121,22 @@ latin1 varchar(300) character set 'utf8' null,
latin1bin varchar(300) character set utf8 collate utf8_bin null,
cp1251 varchar(300) character set 'utf8' null,
guid char(36) null,
- guidbin char(36) binary null
+ guidbin char(36) binary null,
+ nonguid_utf8 varchar(36) character set 'utf8mb4' null
);
-insert into datatypes_strings(utf8, utf8bin, latin1, latin1bin, cp1251, guid, guidbin)
+insert into datatypes_strings(utf8, utf8bin, latin1, latin1bin, cp1251, guid, guidbin, nonguid_utf8)
values
- (null, null, null, null, null, null, null),
- ('', '', '', '', '', '00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000000'),
- ('ASCII', 'ASCII', 'ASCII', 'ASCII', 'ASCII', '00000000-0000-0000-c000-000000000046', '00000000-0000-0000-c000-000000000046'),
- ('Ũńıċōđĕ', 'Ũńıċōđĕ', 'Lãtïñ', 'Lãtïñ', 'АБВГабвг', 'fd24a0e8-c3f2-4821-a456-35da2dc4bb8f', 'fd24a0e8-c3f2-4821-a456-35da2dc4bb8f'),
+ (null, null, null, null, null, null, null, null),
+ ('', '', '', '', '', '00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000000', ''),
+ ('ASCII', 'ASCII', 'ASCII', 'ASCII', 'ASCII', '00000000-0000-0000-c000-000000000046', '00000000-0000-0000-c000-000000000046', 'ASCII'),
+ ('Ũńıċōđĕ', 'Ũńıċōđĕ', 'Lãtïñ', 'Lãtïñ', 'АБВГабвг', 'fd24a0e8-c3f2-4821-a456-35da2dc4bb8f', 'fd24a0e8-c3f2-4821-a456-35da2dc4bb8f', 'Ũńıċōđĕ'),
('This string has exactly 251 characters in it. The encoded length is stored as 0xFC 0xFB 0x00. 0xFB (i.e., 251) is the sentinel byte indicating ""this field is null"". Incorrectly interpreting the (decoded) length as the sentinel byte would corrupt data.',
'This string has exactly 251 characters in it. The encoded length is stored as 0xFC 0xFB 0x00. 0xFB (i.e., 251) is the sentinel byte indicating ""this field is null"". Incorrectly interpreting the (decoded) length as the sentinel byte would corrupt data.',
'This string has exactly 251 characters in it. The encoded length is stored as 0xFC 0xFB 0x00. 0xFB (i.e., 251) is the sentinel byte indicating ""this field is null"". Incorrectly interpreting the (decoded) length as the sentinel byte would corrupt data.',
'This string has exactly 251 characters in it. The encoded length is stored as 0xFC 0xFB 0x00. 0xFB (i.e., 251) is the sentinel byte indicating ""this field is null"". Incorrectly interpreting the (decoded) length as the sentinel byte would corrupt data.',
'This string has exactly 251 characters in it. The encoded length is stored as 0xFC 0xFB 0x00. 0xFB (i.e., 251) is the sentinel byte indicating ""this field is null"". Incorrectly interpreting the (decoded) length as the sentinel byte would corrupt data.',
- '6a0e0a40-6228-11d3-a996-0050041896c8', '6a0e0a40-6228-11d3-a996-0050041896c8');
+ '6a0e0a40-6228-11d3-a996-0050041896c8', '6a0e0a40-6228-11d3-a996-0050041896c8', 'This string has 36 characters in it.');
drop table if exists datatypes_blobs;
create table datatypes_blobs(
@@ -236,6 +237,7 @@ insert into datatypes_json_core (value)
('{""a"": ""b""}');
");
}
+
Connection.Close();
}
}
diff --git a/tests/SideBySide/DatabaseFixture.cs b/tests/SideBySide/DatabaseFixture.cs
index 98193c363..b4c49bd50 100644
--- a/tests/SideBySide/DatabaseFixture.cs
+++ b/tests/SideBySide/DatabaseFixture.cs
@@ -53,6 +53,6 @@ protected virtual void Dispose(bool disposing)
}
}
- static readonly object s_lock = new();
- static bool s_isInitialized;
+ private static readonly object s_lock = new();
+ private static bool s_isInitialized;
}
diff --git a/tests/SideBySide/InsertTests.cs b/tests/SideBySide/InsertTests.cs
index 9cf3c1b85..28fd3509d 100644
--- a/tests/SideBySide/InsertTests.cs
+++ b/tests/SideBySide/InsertTests.cs
@@ -95,7 +95,7 @@ public void InsertTime(int precision)
using (var reader = command.ExecuteReader())
{
Assert.True(reader.Read());
- if(precision == 0)
+ if (precision == 0)
Assert.Equal(TimeSpan.Zero, reader.GetValue(0));
else
Assert.Equal(TimeSpan.FromMilliseconds(10), reader.GetValue(0));
@@ -113,7 +113,7 @@ public void InsertDateTimeOffset()
{
m_database.Connection.Execute(@"drop table if exists insert_datetimeoffset;
create table insert_datetimeoffset(rowid integer not null primary key auto_increment, datetimeoffset1 datetime null);");
- var value = new DateTimeOffsetValues { datetimeoffset1 = new DateTimeOffset(2017, 1, 2, 3, 4, 5, TimeSpan.FromMinutes(678)) };
+ var value = new DateTimeOffsetValues { DateTimeOffset1 = new DateTimeOffset(2017, 1, 2, 3, 4, 5, TimeSpan.FromMinutes(678)) };
m_database.Connection.Open();
try
@@ -124,7 +124,7 @@ public void InsertDateTimeOffset()
{
ParameterName = "@datetimeoffset1",
DbType = DbType.DateTimeOffset,
- Value = value.datetimeoffset1
+ Value = value.DateTimeOffset1,
});
Assert.Equal(1, cmd.ExecuteNonQuery());
}
@@ -137,7 +137,7 @@ public void InsertDateTimeOffset()
DateTime.SpecifyKind(datetime, DateTimeKind.Utc);
- Assert.Equal(value.datetimeoffset1.Value.UtcDateTime, datetime);
+ Assert.Equal(value.DateTimeOffset1.Value.UtcDateTime, datetime);
}
[SkippableFact(Baseline = "https://bugs.mysql.com/bug.php?id=91199")]
@@ -145,7 +145,7 @@ public void InsertSingleStoreDateTime()
{
m_database.Connection.Execute(@"drop table if exists insert_mysqldatetime;
create table insert_mysqldatetime(rowid integer not null primary key auto_increment, ts timestamp(6) null);");
- var value = new DateTimeOffsetValues { datetimeoffset1 = new DateTimeOffset(2017, 1, 2, 3, 4, 5, TimeSpan.FromMinutes(678)) };
+ var value = new DateTimeOffsetValues { DateTimeOffset1 = new DateTimeOffset(2017, 1, 2, 3, 4, 5, TimeSpan.FromMinutes(678)) };
m_database.Connection.Open();
try
@@ -178,7 +178,7 @@ public void InsertGeography(bool prepare)
using var cmd = m_database.Connection.CreateCommand();
cmd.CommandText = @"insert into insert_singlestoregeography(shape) values(@shape);";
cmd.Parameters.AddWithValue("@shape", new SingleStoreGeography("POLYGON((3 3,4 3,4 4,3 4,3 3))"));
- if(prepare)
+ if (prepare)
cmd.Prepare();
Assert.Equal(1, cmd.ExecuteNonQuery());
}
@@ -460,40 +460,39 @@ public async Task EnumParametersAreParsedCorrectly()
{
m_database.Connection.Close();
}
-
}
- enum Enum16 : short
+ private enum Enum16 : short
{
Off,
On,
}
- enum Enum32 : int
+ private enum Enum32 : int
{
Off,
On,
}
- enum Enum64 : long
+ private enum Enum64 : long
{
Off,
On,
}
- class DateTimeOffsetValues
+ private class DateTimeOffsetValues
{
- public DateTimeOffset? datetimeoffset1 { get; set; }
+ public DateTimeOffset? DateTimeOffset1 { get; set; }
}
- class ColorEnumValues
+ private class ColorEnumValues
{
public string Varchar { get; set; }
public string String { get; set; }
public int Int { get; set; }
}
- class EnumValues
+ private class EnumValues
{
public Enum16? Enum16 { get; set; }
public Enum32? Enum32 { get; set; }
@@ -514,17 +513,17 @@ color enum('red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet') not n
Assert.Equal(new[] { "blue" }, m_database.Connection.Query(@"select color from insert_mysql_enums"));
}
- enum SingleStoreSize
+ private enum SingleStoreSize
{
None,
XSmall,
Small,
Medium,
Large,
- XLarge
+ XLarge,
}
- enum SingleStoreColor
+ private enum SingleStoreColor
{
None,
Red,
@@ -533,7 +532,7 @@ enum SingleStoreColor
Green,
Blue,
Indigo,
- Violet
+ Violet,
}
[Fact]
@@ -548,7 +547,6 @@ value set('""one""', '""two""', '""four""', '""eight""') null
Assert.Equal(new[] { "\"one\"", "\"one\",\"two\"", "\"one\",\"four\"", "\"one\",\"two\",\"four\"" }, m_database.Connection.Query(@"select value from insert_mysql_set where JSON_ARRAY_CONTAINS_STRING(concat('[', value, ']'), 'one') order by rowid"));
}
-
#if !BASELINE
[Theory]
[MemberData(nameof(GetBlobs))]
@@ -588,5 +586,5 @@ public static IEnumerable GetBlobs()
}
#endif
- readonly DatabaseFixture m_database;
+ private readonly DatabaseFixture m_database;
}
diff --git a/tests/SideBySide/LoadDataInfileAsync.cs b/tests/SideBySide/LoadDataInfileAsync.cs
index f414e61e9..6c957aa73 100644
--- a/tests/SideBySide/LoadDataInfileAsync.cs
+++ b/tests/SideBySide/LoadDataInfileAsync.cs
@@ -66,7 +66,7 @@ public async Task ThrowsNotSupportedExceptionForNotTrustedHostAndNotStream()
await Assert.ThrowsAsync(async () => await command.ExecuteNonQueryAsync());
}
- readonly DatabaseFixture m_database;
- readonly string m_testTable;
- readonly string m_loadDataInfileCommand;
+ private readonly DatabaseFixture m_database;
+ private readonly string m_testTable;
+ private readonly string m_loadDataInfileCommand;
}
diff --git a/tests/SideBySide/LoadDataInfileSync.cs b/tests/SideBySide/LoadDataInfileSync.cs
index ecdfa87f6..477f9a7db 100644
--- a/tests/SideBySide/LoadDataInfileSync.cs
+++ b/tests/SideBySide/LoadDataInfileSync.cs
@@ -63,7 +63,7 @@ public void ThrowsNotSupportedExceptionForNotTrustedHostAndNotStream()
m_database.Connection.Close();
}
- readonly DatabaseFixture m_database;
- readonly string m_testTable;
- readonly string m_loadDataInfileCommand;
+ private readonly DatabaseFixture m_database;
+ private readonly string m_testTable;
+ private readonly string m_loadDataInfileCommand;
}
diff --git a/tests/SideBySide/ParameterCollection.cs b/tests/SideBySide/ParameterCollection.cs
index fce278e00..26266a5bc 100644
--- a/tests/SideBySide/ParameterCollection.cs
+++ b/tests/SideBySide/ParameterCollection.cs
@@ -299,6 +299,6 @@ public void SetTwoParametersToSameNAme()
#endif
}
- readonly SingleStoreCommand m_command;
- readonly SingleStoreParameterCollection m_parameterCollection;
+ private readonly SingleStoreCommand m_command;
+ private readonly SingleStoreParameterCollection m_parameterCollection;
}
diff --git a/tests/SideBySide/ParameterTests.cs b/tests/SideBySide/ParameterTests.cs
index 727635590..3579d463c 100644
--- a/tests/SideBySide/ParameterTests.cs
+++ b/tests/SideBySide/ParameterTests.cs
@@ -31,7 +31,7 @@ public void DbTypeToSingleStoreDbType(DbType dbType, SingleStoreDbType mySqlDbTy
[InlineData(new[] { DbType.Date }, new[] { SingleStoreDbType.Date, SingleStoreDbType.Newdate })]
#if !BASELINE
[InlineData(new[] { DbType.Int32 }, new[] { SingleStoreDbType.Int32, SingleStoreDbType.Year })]
- [InlineData(new[] { DbType.Binary }, new[] { SingleStoreDbType.Blob, SingleStoreDbType.Binary, SingleStoreDbType.TinyBlob, SingleStoreDbType.MediumBlob, SingleStoreDbType.LongBlob})]
+ [InlineData(new[] { DbType.Binary }, new[] { SingleStoreDbType.Blob, SingleStoreDbType.Binary, SingleStoreDbType.TinyBlob, SingleStoreDbType.MediumBlob, SingleStoreDbType.LongBlob })]
[InlineData(new[] { DbType.String, DbType.AnsiString, DbType.Xml },
new[] { SingleStoreDbType.VarChar, SingleStoreDbType.VarString, SingleStoreDbType.Text, SingleStoreDbType.TinyText, SingleStoreDbType.MediumText, SingleStoreDbType.LongText, SingleStoreDbType.JSON, SingleStoreDbType.Enum, SingleStoreDbType.Set, SingleStoreDbType.Geography, SingleStoreDbType.GeographyPoint })]
[InlineData(new[] { DbType.Decimal, DbType.Currency }, new[] { SingleStoreDbType.NewDecimal, SingleStoreDbType.Decimal })]
@@ -333,7 +333,6 @@ public void SetValueToByteArrayInfersType()
Assert.Equal(SingleStoreDbType.Blob, parameter.SingleStoreDbType);
}
-
[Fact]
public void SetValueDoesNotInferType()
{
@@ -457,13 +456,12 @@ PRIMARY KEY (`Id`)
INSERT INTO zeroByteEscaping VALUES(1, BINARY('\012\0\0'));
");
- using (var command = new SingleStoreCommand(@"CREATE TABLE zeroByteEscapingCTAS as SELECT * FROM zeroByteEscaping WHERE Content=@content", connection))
- {
- command.Parameters.AddWithValue("@content", new byte[] {0x00, 0x31, 0x32, 0x00, 0x00});
- Assert.False(command.IsPrepared);
- command.ExecuteNonQuery();
- }
-
+ using (var command = new SingleStoreCommand(@"CREATE TABLE zeroByteEscapingCTAS as SELECT * FROM zeroByteEscaping WHERE Content=@content", connection))
+ {
+ command.Parameters.AddWithValue("@content", new byte[] { 0x00, 0x31, 0x32, 0x00, 0x00 });
+ Assert.False(command.IsPrepared);
+ command.ExecuteNonQuery();
+ }
using (var command = new SingleStoreCommand(@"SELECT COUNT(*) FROM `zeroByteEscapingCTAS` WHERE BINARY(`Content`) = 0x0031320000", connection))
{
diff --git a/tests/SideBySide/QueryTests.cs b/tests/SideBySide/QueryTests.cs
index d2a74a562..ebb03baf3 100644
--- a/tests/SideBySide/QueryTests.cs
+++ b/tests/SideBySide/QueryTests.cs
@@ -1,3 +1,4 @@
+using System.Runtime.InteropServices;
using SingleStoreConnector.Protocol;
namespace SideBySide;
@@ -1387,6 +1388,7 @@ END AS value
Assert.True(await reader.NextResultAsync());
Assert.True(await reader.ReadAsync());
+
// MySQL returns ulong, MariaDB returns decimal; GetBoolean will coerce both
Assert.True(reader.GetBoolean(0));
Assert.False(await reader.ReadAsync());
@@ -1413,7 +1415,7 @@ public void GetIntForTinyInt1(bool prepare)
using var reader = command.ExecuteReader();
- int[] expected = {-128, -1, 0, 1, 2, 127};
+ int[] expected = { -128, -1, 0, 1, 2, 127 };
for (int i = 0; i < expected.Length; i++)
{
@@ -1663,7 +1665,7 @@ public void GetBytesByName()
[Fact]
public void ServerDoesNotSendMariaDbCacheMetadataOrQueryAttributes()
{
- using var connection = new SingleStoreConnection(AppConfig.ConnectionString);
+ using var connection = new SingleStoreConnection(AppConfig.ConnectionString);
connection.Open();
var serverCapabilities = connection.Session.ServerCapabilities;
@@ -1676,13 +1678,13 @@ public void ServerDoesNotSendMariaDbCacheMetadataOrQueryAttributes()
"Server should not send QueryAttributes capability flag.");
}
- class BoolTest
+ private class BoolTest
{
public int Id { get; set; }
public bool? IsBold { get; set; }
}
- class UseReaderWithoutDisposingThreadData
+ private class UseReaderWithoutDisposingThreadData
{
public UseReaderWithoutDisposingThreadData(List exceptions, SingleStoreConnectionStringBuilder csb)
{
@@ -1695,10 +1697,10 @@ public UseReaderWithoutDisposingThreadData(List exceptions, SingleSto
public SingleStoreConnectionStringBuilder ConnectionStringBuilder { get; }
}
- enum TestLongEnum : long
+ private enum TestLongEnum : long
{
Value = long.MaxValue,
}
- readonly DatabaseFixture m_database;
+ private readonly DatabaseFixture m_database;
}
diff --git a/tests/SideBySide/RedirectionTests.cs b/tests/SideBySide/RedirectionTests.cs
index 98da055d2..676a56ef0 100644
--- a/tests/SideBySide/RedirectionTests.cs
+++ b/tests/SideBySide/RedirectionTests.cs
@@ -79,31 +79,25 @@ public void RedirectionTest()
Assert.Equal(proxy.ListenPort, db.SessionEndPoint!.Port);
db.Close();
}
-
- } finally{
- m_database.Connection.Execute(
- $"set @@global.redirect_url=\"\"");
+ }
+ finally
+ {
+ m_database.Connection.Execute($"set @@global.redirect_url=\"\"");
}
SingleStoreConnection.ClearAllPools();
+
// ensure that when required, throwing error if no redirection
csb.ServerRedirectionMode = SingleStoreServerRedirectionMode.Required;
using (var db = new SingleStoreConnection(csb.ConnectionString))
{
- try
- {
- db.Open();
- Assert.Fail("must have thrown error");
- }
- catch (SingleStoreException ex)
- {
- Assert.Equal((int) SingleStoreErrorCode.UnableToConnectToHost, ex.Number);
- }
+ var exception = Assert.Throws(db.Open);
+ Assert.Equal(SingleStoreErrorCode.UnableToConnectToHost, exception.ErrorCode);
}
StopProxy();
}
- protected void StartProxy()
+ protected void StartProxy()
{
var csb = AppConfig.CreateConnectionStringBuilder();
proxy = new ServerConfiguration( csb.Server, (int)csb.Port );
@@ -111,36 +105,38 @@ protected void StartProxy()
serverThread.Start( proxy );
}
- protected void StopProxy()
+ protected void StopProxy()
{
proxy.RunServer = false;
proxy.ServerSocket.Close();
}
- private class ServerConfiguration {
-
- public IPAddress RemoteAddress;
- public int RemotePort;
- public int ListenPort;
- public Socket ServerSocket;
- public ServerConfiguration(String remoteAddress, int remotePort) {
+ private class ServerConfiguration
+ {
+ public IPAddress RemoteAddress { get; set; }
+ public int RemotePort { get; set; }
+ public int ListenPort { get; set; }
+ public Socket ServerSocket { get; set; }
+ public ServerConfiguration(string remoteAddress, int remotePort) {
var ipHostEntry = Dns.GetHostEntry(remoteAddress);
RemoteAddress = ipHostEntry.AddressList[0];
- RemotePort = remotePort;
- ListenPort = 0;
+ RemotePort = remotePort;
+ ListenPort = 0;
}
- public bool RunServer = true;
+ public bool RunServer { get; set; } = true;
}
- private static void ServerThread(Object configObj) {
- ServerConfiguration config = (ServerConfiguration)configObj;
- Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
-
- serverSocket.Bind( new IPEndPoint( IPAddress.Any, 0 ) );
- serverSocket.Listen(1);
- config.ListenPort = ((IPEndPoint) serverSocket.LocalEndPoint).Port;
- config.ServerSocket = serverSocket;
- while( config.RunServer ) {
+ private static void ServerThread(object configObj)
+ {
+ ServerConfiguration config = (ServerConfiguration)configObj;
+ Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+ serverSocket.Bind( new IPEndPoint( IPAddress.Any, 0 ) );
+ serverSocket.Listen(1);
+ config.ListenPort = ((IPEndPoint) serverSocket.LocalEndPoint).Port;
+ config.ServerSocket = serverSocket;
+ while (config.RunServer)
+ {
try
{
Socket client = serverSocket.Accept();
@@ -151,43 +147,50 @@ private static void ServerThread(Object configObj) {
{
return;
}
- }
- }
+ }
+ }
- private class ClientContext {
- public ServerConfiguration Config;
- public Socket Client;
- }
+ private class ClientContext
+ {
+ public ServerConfiguration Config { get; set; }
+ public Socket Client { get; set; }
+ }
- private static void ClientThread(Object contextObj) {
- ClientContext context = (ClientContext)contextObj;
- Socket client = context.Client;
- ServerConfiguration config = context.Config;
- IPEndPoint remoteEndPoint = new IPEndPoint( config.RemoteAddress, config.RemotePort );
- Socket remote = new Socket( remoteEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
- remote.Connect( remoteEndPoint );
- Byte[] buffer = new Byte[4096];
- for(;;) {
- if (!config.RunServer)
- {
- remote.Close();
- client.Close();
- return;
- }
- if( client.Available > 0 ) {
- var count = client.Receive( buffer );
- if( count == 0 ) return;
- remote.Send( buffer, count, SocketFlags.None );
- }
- if( remote.Available > 0 ) {
- var count = remote.Receive( buffer );
- if( count == 0 ) return;
- client.Send( buffer, count, SocketFlags.None );
- }
- }
- }
+ private static void ClientThread(object contextObj)
+ {
+ ClientContext context = (ClientContext) contextObj;
+ Socket client = context.Client;
+ ServerConfiguration config = context.Config;
+ IPEndPoint remoteEndPoint = new IPEndPoint(config.RemoteAddress, config.RemotePort);
+ Socket remote = new Socket(remoteEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+ remote.Connect(remoteEndPoint);
+ var buffer = new byte[4096];
+ while (true)
+ {
+ if (!config.RunServer)
+ {
+ remote.Close();
+ client.Close();
+ return;
+ }
+ if (client.Available > 0)
+ {
+ var count = client.Receive(buffer);
+ if (count == 0)
+ return;
+ remote.Send(buffer, count, SocketFlags.None);
+ }
+ if (remote.Available > 0)
+ {
+ var count = remote.Receive(buffer);
+ if (count == 0)
+ return;
+ client.Send(buffer, count, SocketFlags.None);
+ }
+ }
+ }
- readonly DatabaseFixture m_database;
+ private readonly DatabaseFixture m_database;
private ServerConfiguration proxy;
}
#endif
diff --git a/tests/SideBySide/SchemaProviderTests.cs b/tests/SideBySide/SchemaProviderTests.cs
index ff7976ae2..431c031b3 100644
--- a/tests/SideBySide/SchemaProviderTests.cs
+++ b/tests/SideBySide/SchemaProviderTests.cs
@@ -121,10 +121,10 @@ public void MetaDataCollectionsRestriction() =>
[InlineData("datatypes", "DataTypes")]
[InlineData("Indexes")]
[InlineData("IndexColumns")]
- // only in 8.0 - [InlineData("KeyWords")]
+ //// only in 8.0 - [InlineData("KeyWords")]
[InlineData("MetaDataCollections")]
[InlineData("Procedures")]
- // only in 8.0 - [InlineData("ResourceGroups")]
+ //// only in 8.0 - [InlineData("ResourceGroups")]
[InlineData("Tables")]
[InlineData("Triggers")]
[InlineData("Views")]
@@ -237,5 +237,5 @@ public void IndexColumnsWithColumnName()
Assert.Equal(expected, actual);
}
- readonly DatabaseFixture m_database;
+ private readonly DatabaseFixture m_database;
}
diff --git a/tests/SideBySide/ServerFeatures.cs b/tests/SideBySide/ServerFeatures.cs
index 9a3dab829..973491506 100644
--- a/tests/SideBySide/ServerFeatures.cs
+++ b/tests/SideBySide/ServerFeatures.cs
@@ -40,4 +40,9 @@ public enum ServerFeatures
/// Server provides hash of TLS certificate in first OK packet.
///
TlsFingerprintValidation = 0x100_0000,
+
+ ///
+ /// Server supports the 'parsec' authentication plugin.
+ ///
+ ParsecAuthentication = 0x200_0000,
}
diff --git a/tests/SideBySide/SideBySide.csproj b/tests/SideBySide/SideBySide.csproj
index d5c0a908e..861c2b8e6 100644
--- a/tests/SideBySide/SideBySide.csproj
+++ b/tests/SideBySide/SideBySide.csproj
@@ -1,12 +1,12 @@
- net462;net481;net6.0;net8.0;net9.0
+ net462;net481;net6.0;net8.0;net10.0
false
- net9.0
+ net10.0
BASELINE
$(NoWarn);MSB3246
@@ -17,15 +17,15 @@
true
..\..\SingleStoreConnector.snk
true
+ true
true
SideBySide
SideBySide
true
true
64
- 11.0
enable
- $(NoWarn);xUnit1030
+ $(NoWarn);SA0001;SA1021;SA1133;xUnit1030
@@ -41,7 +41,6 @@
-
diff --git a/tests/SideBySide/SingleStoreDataSourceTests.cs b/tests/SideBySide/SingleStoreDataSourceTests.cs
index c53c9d0cd..b7d751d2f 100644
--- a/tests/SideBySide/SingleStoreDataSourceTests.cs
+++ b/tests/SideBySide/SingleStoreDataSourceTests.cs
@@ -4,7 +4,7 @@ namespace SideBySide;
public class SingleStoreDataSourceTests : IClassFixture
{
- public SingleStoreDataSourceTests(DatabaseFixture _)
+ public SingleStoreDataSourceTests(DatabaseFixture ignoredFixture)
{
}
diff --git a/tests/SideBySide/SslTests.cs b/tests/SideBySide/SslTests.cs
index a2034e349..ac721521b 100644
--- a/tests/SideBySide/SslTests.cs
+++ b/tests/SideBySide/SslTests.cs
@@ -259,6 +259,20 @@ public async Task ConnectZeroConfigurationSslEd25519()
using var connection = new SingleStoreConnection(csb.ConnectionString);
await connection.OpenAsync();
}
+
+ [SkippableFact(ServerFeatures.TlsFingerprintValidation | ServerFeatures.ParsecAuthentication)]
+ public async Task ConnectZeroConfigurationSslParsec()
+ {
+ SingleStoreConnector.Authentication.Ed25519.ParsecAuthenticationPlugin.Install();
+ var csb = AppConfig.CreateConnectionStringBuilder();
+ csb.CertificateFile = null;
+ csb.SslMode = SingleStoreSslMode.VerifyFull;
+ csb.SslCa = "";
+ csb.UserID = "parsec-user";
+ csb.Password = "P@rs3c-Pa55";
+ using var connection = new SingleStoreConnection(csb.ConnectionString);
+ await connection.OpenAsync();
+ }
#endif
[SkippableFact(ConfigSettings.RequiresSsl)]
@@ -316,5 +330,5 @@ public async Task ForceTls11()
}
#endif
- readonly DatabaseFixture m_database;
+ private readonly DatabaseFixture m_database;
}
diff --git a/tests/SideBySide/StoredProcedureTests.cs b/tests/SideBySide/StoredProcedureTests.cs
index 6ef5cfac1..720df75e1 100644
--- a/tests/SideBySide/StoredProcedureTests.cs
+++ b/tests/SideBySide/StoredProcedureTests.cs
@@ -1,3 +1,5 @@
+using System.Runtime.InteropServices;
+
namespace SideBySide;
public class StoredProcedureTests : IClassFixture
@@ -88,7 +90,7 @@ public void CallFailingFunctionInTransaction()
}
[SkippableTheory(ServerFeatures.StoredProcedures)]
- // [InlineData("FUNCTION", false)] see PLAT-6053
+ //// [InlineData("FUNCTION", false)] see PLAT-6053
[InlineData("PROCEDURE", true)]
[InlineData("PROCEDURE", false)]
public async Task StoredProcedureEchoException(string procedureType, bool prepare)
@@ -207,8 +209,8 @@ public async Task StoredProcedureReturnsNull(bool prepare)
var reader = (SingleStoreDataReader) await cmd.ExecuteReaderAsync();
var result = await reader.ReadAsync();
Assert.True(result);
- Assert.Equal(DBNull.Value, reader.GetValue(0));
- Assert.Equal(DBNull.Value, reader.GetValue(1));
+ Assert.Equal(DBNull.Value, reader.GetValue(0));
+ Assert.Equal(DBNull.Value, reader.GetValue(1));
}
[Theory]
@@ -304,12 +306,20 @@ private async Task CircleAssertions(DbCommand cmd, string executorType)
{
var reader = (SingleStoreDataReader) await cmd.ExecuteReaderAsync();
var result = await reader.ReadAsync();
+
Assert.True(result);
- Assert.Equal(2 * (double) cmd.Parameters["@radius"].Value, reader.GetDouble("diameter"));
- Assert.Equal(2.0 * Math.PI * (double) cmd.Parameters["@radius"].Value, reader.GetDouble("circumference"));
- Assert.Equal(Math.PI * Math.Pow((double) cmd.Parameters["@radius"].Value, 2), reader.GetDouble("area"));
- Assert.Equal(reader.GetDouble("area") * (double) cmd.Parameters["@height"].Value, reader.GetDouble("volume"));
- } else {
+
+ var radius = (double)cmd.Parameters["@radius"].Value;
+ var height = (double)cmd.Parameters["@height"].Value;
+ var area = reader.GetDouble("area");
+
+ Assert.Equal(2 * radius, reader.GetDouble("diameter"));
+ Assert.Equal(2.0 * Math.PI * radius, reader.GetDouble("circumference"));
+ Assert.Equal(Math.PI * Math.Pow(radius, 2), area);
+ Assert.Equal(area * height, reader.GetDouble("volume"));
+ }
+ else
+ {
var result = await ExecuteCommandAsync(cmd, executorType);
if (executorType != "NonQuery")
Assert.Equal((string) cmd.Parameters["@name"].Value + "circle", result);
@@ -409,7 +419,7 @@ public async Task ParameterLoop(bool prepare)
ParameterName = "high",
DbType = DbType.Int32,
Direction = ParameterDirection.Input,
- Value = 1
+ Value = 1,
};
while ((int) parameter.Value < 8)
{
@@ -546,11 +556,11 @@ public void DeriveParametersDoesNotExistThenIsCreated()
#endif
[InlineData("char(30)", 30)]
[InlineData("varchar(50)", 50)]
- // These return nonzero sizes for some versions of MySQL Server 8.0
- // [InlineData("bit", 0)]
- // [InlineData("tinyint", 0)]
- // [InlineData("bigint", 0)]
- // [InlineData("bigint unsigned", 0)]
+ //// These return nonzero sizes for some versions of MySQL Server 8.0
+ //// [InlineData("bit", 0)]
+ //// [InlineData("tinyint", 0)]
+ //// [InlineData("bigint", 0)]
+ //// [InlineData("bigint unsigned", 0)]
public void DeriveParametersParameterSize(string parameterType, int expectedSize)
{
var csb = AppConfig.CreateConnectionStringBuilder();
@@ -575,7 +585,7 @@ public void DeriveParametersParameterSize(string parameterType, int expectedSize
[InlineData("bit(1)", SingleStoreDbType.Bit)]
#if BASELINE
[InlineData("bool", SingleStoreDbType.Byte)]
- [InlineData("tinyint(1)", MySqlDbType.Byte)]
+ [InlineData("tinyint(1)", SingleStoreDbType.Byte)]
#else
[InlineData("bool", SingleStoreDbType.Bool)]
[InlineData("tinyint(1)", SingleStoreDbType.Bool)]
@@ -737,6 +747,65 @@ public void SprocNameSpecialCharacters(string sprocName)
}
}
+#if !BASELINE
+ [Theory]
+ [InlineData(SingleStoreGuidFormat.Binary16, "BINARY(16)", "X'BABD8384C908499C9D95C02ADA94A970'", false)]
+ [InlineData(SingleStoreGuidFormat.Binary16, "BINARY(16)", "X'BABD8384C908499C9D95C02ADA94A970'", true)]
+ [InlineData(SingleStoreGuidFormat.Char32, "CHAR(32)", "'BABD8384C908499C9D95C02ADA94A970'", false)]
+ [InlineData(SingleStoreGuidFormat.Char32, "CHAR(32)", "'BABD8384C908499C9D95C02ADA94A970'", true)]
+ [InlineData(SingleStoreGuidFormat.Char36, "CHAR(36)", "'BABD8384-C908-499C-9D95-C02ADA94A970'", false)]
+ [InlineData(SingleStoreGuidFormat.Char36, "CHAR(36)", "'BABD8384-C908-499C-9D95-C02ADA94A970'", true)]
+ public void StoredProcedureReturnsGuid(SingleStoreGuidFormat guidFormat, string columnDefinition, string columnValue, bool prepare)
+ {
+ if (prepare)
+ {
+ return; // SingleStore does not currently support preparing ECHO proc()
+ }
+
+ var csb = AppConfig.CreateConnectionStringBuilder();
+ csb.GuidFormat = guidFormat;
+ csb.Pooling = false;
+
+ using var connection = new SingleStoreConnection(csb.ConnectionString);
+ connection.Open();
+
+ using (var setup = new SingleStoreCommand($"""
+ DROP TABLE IF EXISTS out_guid_table;
+ CREATE TABLE out_guid_table
+ (
+ id BIGINT PRIMARY KEY AUTO_INCREMENT,
+ guid {columnDefinition}
+ );
+ INSERT INTO out_guid_table (guid) VALUES ({columnValue});
+
+ DROP PROCEDURE IF EXISTS out_guid;
+ CREATE PROCEDURE out_guid() RETURNS {columnDefinition} AS
+ DECLARE g {columnDefinition};
+ BEGIN
+ SELECT guid INTO g
+ FROM out_guid_table
+ LIMIT 1;
+
+ RETURN g;
+ END;
+ """, connection))
+ {
+ setup.ExecuteNonQuery();
+ }
+
+ using var command = new SingleStoreCommand("ECHO out_guid()", connection);
+
+ if (prepare)
+ command.Prepare();
+
+ var value = command.ExecuteScalar();
+
+ Assert.Equal(
+ new Guid("BABD8384-C908-499C-9D95-C02ADA94A970"),
+ Assert.IsType(value));
+ }
+#endif
+
private static string NormalizeSpaces(string input)
{
input = input.Replace('\r', ' ');
@@ -758,5 +827,5 @@ private static SingleStoreConnection CreateOpenConnection()
return connection;
}
- readonly DatabaseFixture m_database;
+ private readonly DatabaseFixture m_database;
}
diff --git a/tests/SideBySide/TestUtilities.cs b/tests/SideBySide/TestUtilities.cs
index db828c2fc..ee92b7783 100644
--- a/tests/SideBySide/TestUtilities.cs
+++ b/tests/SideBySide/TestUtilities.cs
@@ -18,7 +18,7 @@ public static void AssertEqual(byte[] expected, byte[] actual)
}
///
- /// Verifies that is an integer ( or ) with the value 1.
+ /// Verifies that is an integer ( or ) with the value 1.
///
public static void AssertIsOne(object value)
{
@@ -80,7 +80,7 @@ private static async Task AssertExecuteScalarReturnsOneOrThrowsExceptionAsync(Si
else
{
var ex = await Assert.ThrowsAnyAsync(async () => await command.ExecuteScalarAsync(token));
- var exception = ex as SingleStoreException;
+ SingleStoreException exception = ex as SingleStoreException;
while (exception is null && ex is not null)
{
ex = ex.InnerException;
diff --git a/tests/SideBySide/Transaction.cs b/tests/SideBySide/Transaction.cs
index af458b1dd..868b6fd4d 100644
--- a/tests/SideBySide/Transaction.cs
+++ b/tests/SideBySide/Transaction.cs
@@ -377,6 +377,6 @@ public async Task DisposeAsync()
}
#endif
- readonly TransactionFixture m_database;
- readonly SingleStoreConnection m_connection;
+ private readonly TransactionFixture m_database;
+ private readonly SingleStoreConnection m_connection;
}
diff --git a/tests/SideBySide/TransactionScopeTests.cs b/tests/SideBySide/TransactionScopeTests.cs
index 991f3d89f..cbd60c633 100644
--- a/tests/SideBySide/TransactionScopeTests.cs
+++ b/tests/SideBySide/TransactionScopeTests.cs
@@ -9,13 +9,13 @@ public TransactionScopeTests(DatabaseFixture database)
m_database = database;
}
- public static IEnumerable ConnectionStrings = new[]
+ public static IEnumerable ConnectionStrings { get; } = new[]
{
#if BASELINE
new object[] { "" },
#else
new object[] { "UseXaTransactions=False" },
- // new object[] { "UseXaTransactions=True" }, no XA transactions in SingleStore
+ //// new object[] { "UseXaTransactions=True" }, no XA transactions in SingleStore
#endif
};
@@ -274,7 +274,7 @@ public void UsingSequentialConnectionsInOneTransactionDoesNotDeadlock(string con
var transactionOptions = new TransactionOptions
{
IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted,
- Timeout = TransactionManager.MaximumTimeout
+ Timeout = TransactionManager.MaximumTimeout,
};
using (var scope = new TransactionScope(TransactionScopeOption.Required, transactionOptions, TransactionScopeAsyncFlowOption.Enabled))
{
@@ -318,7 +318,7 @@ public void UsingSequentialConnectionsInOneTransactionDoesNotDeadlockWithoutComp
var transactionOptions = new TransactionOptions
{
IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted,
- Timeout = TransactionManager.MaximumTimeout
+ Timeout = TransactionManager.MaximumTimeout,
};
using (new TransactionScope(TransactionScopeOption.Required, transactionOptions, TransactionScopeAsyncFlowOption.Enabled))
{
@@ -370,7 +370,7 @@ public void UsingSequentialConnectionsInOneTransactionWithoutAutoEnlistDoesNotDe
{
connection.Open();
connection.EnlistTransaction(transaction);
- connection.Execute("update transaction_scope_test set value = @newValue where rowid = @id", new { newValue = "new value", id = command.LastInsertedId});
+ connection.Execute("update transaction_scope_test set value = @newValue where rowid = @id", new { newValue = "new value", id = command.LastInsertedId });
}
transaction.Commit();
@@ -458,7 +458,7 @@ public void ReusingConnectionInOneTransactionDoesNotDeadlock(string connectionSt
var transactionOptions = new TransactionOptions
{
IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted,
- Timeout = TransactionManager.MaximumTimeout
+ Timeout = TransactionManager.MaximumTimeout,
};
using (var scope = new TransactionScope(TransactionScopeOption.Required, transactionOptions, TransactionScopeAsyncFlowOption.Enabled))
{
@@ -499,7 +499,7 @@ public void ReusingConnectionInOneTransactionDoesNotDeadlockWithoutComplete(stri
var transactionOptions = new TransactionOptions
{
IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted,
- Timeout = TransactionManager.MaximumTimeout
+ Timeout = TransactionManager.MaximumTimeout,
};
using (new TransactionScope(TransactionScopeOption.Required, transactionOptions, TransactionScopeAsyncFlowOption.Enabled))
{
@@ -881,6 +881,5 @@ public void ConnectionOpenedCallbackAutoEnlistInTransaction()
}
#endif
- DatabaseFixture m_database;
+ private DatabaseFixture m_database;
}
-
diff --git a/tests/SideBySide/UpdateTests.cs b/tests/SideBySide/UpdateTests.cs
index 07f7173f4..f46fa4d96 100644
--- a/tests/SideBySide/UpdateTests.cs
+++ b/tests/SideBySide/UpdateTests.cs
@@ -161,5 +161,5 @@ public void UpdateFieldCount()
}
}
- readonly DatabaseFixture m_database;
+ private readonly DatabaseFixture m_database;
}
diff --git a/tests/SingleStoreConnector.DependencyInjection.Tests/DependencyInjectionTests.cs b/tests/SingleStoreConnector.DependencyInjection.Tests/DependencyInjectionTests.cs
index 8ea85f4f5..8c0cffaf7 100644
--- a/tests/SingleStoreConnector.DependencyInjection.Tests/DependencyInjectionTests.cs
+++ b/tests/SingleStoreConnector.DependencyInjection.Tests/DependencyInjectionTests.cs
@@ -215,5 +215,5 @@ public async Task KeyedDbDataSourceIsRegistered()
Assert.Equal(c_connectionString, connection.ConnectionString);
}
- const string c_connectionString = "Server=localhost;User ID=root;Password=pass";
+ private const string c_connectionString = "Server=localhost;User ID=root;Password=pass";
}
diff --git a/tests/SingleStoreConnector.DependencyInjection.Tests/SingleStoreConnector.DependencyInjection.Tests.csproj b/tests/SingleStoreConnector.DependencyInjection.Tests/SingleStoreConnector.DependencyInjection.Tests.csproj
index 82b767a95..c364bea5b 100644
--- a/tests/SingleStoreConnector.DependencyInjection.Tests/SingleStoreConnector.DependencyInjection.Tests.csproj
+++ b/tests/SingleStoreConnector.DependencyInjection.Tests/SingleStoreConnector.DependencyInjection.Tests.csproj
@@ -1,7 +1,7 @@
- net9.0
+ net10.0
true
true
..\..\SingleStoreConnector.snk
diff --git a/tests/SingleStoreConnector.NativeAot.Tests/SingleStoreConnector.NativeAot.Tests.csproj b/tests/SingleStoreConnector.NativeAot.Tests/SingleStoreConnector.NativeAot.Tests.csproj
index b84356939..2cbf4d232 100644
--- a/tests/SingleStoreConnector.NativeAot.Tests/SingleStoreConnector.NativeAot.Tests.csproj
+++ b/tests/SingleStoreConnector.NativeAot.Tests/SingleStoreConnector.NativeAot.Tests.csproj
@@ -2,7 +2,7 @@
Exe
- net8.0;net9.0
+ net8.0;net10.0
enable
enable
true
diff --git a/tests/SingleStoreConnector.NativeAot.Tests/packages.lock.json b/tests/SingleStoreConnector.NativeAot.Tests/packages.lock.json
index 9dae639b0..e466b627d 100644
--- a/tests/SingleStoreConnector.NativeAot.Tests/packages.lock.json
+++ b/tests/SingleStoreConnector.NativeAot.Tests/packages.lock.json
@@ -1,18 +1,18 @@
{
"version": 2,
"dependencies": {
- "net8.0": {
+ "net10.0": {
"Microsoft.DotNet.ILCompiler": {
"type": "Direct",
- "requested": "[8.0.15, )",
- "resolved": "8.0.15",
- "contentHash": "wMf2N7fJ846aKd73R5gqvtbyqu89/LywlWCtMyXUqKYc9DR3s9kUgNrLIsT9KeRwyinGFJDtRbiib0M4YBX6ZA=="
+ "requested": "[10.0.7, )",
+ "resolved": "10.0.7",
+ "contentHash": "S53itVb3/l5wEyvdOukwxGzRtw7JSKuh2GurW3W5R57w4nxbZZ+7qCu+5jVlowLmgGrQ7yxJLZdpJc1Vpv8aig=="
},
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
- "requested": "[8.0.15, )",
- "resolved": "8.0.15",
- "contentHash": "s4eXlcRGyHeCgFUGQnhq0e/SCHBPp0jOHgMqZg3fQ2OCHJSm1aOUhI6RFWuVIcEb9ig2WgI2kWukk8wu72EbUQ=="
+ "requested": "[10.0.7, )",
+ "resolved": "10.0.7",
+ "contentHash": "Y0O2XrQiNYsW9UM2GhH/8faornu9pYXdd9YBcb/CrBnCOw5QtFqgh8bFd5zjxbrdoSVxGPUn1hWSrKtD0SEPzg=="
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
@@ -24,6 +24,15 @@
"Microsoft.SourceLink.Common": "8.0.0"
}
},
+ "StyleCop.Analyzers": {
+ "type": "Direct",
+ "requested": "[1.2.0-beta.556, )",
+ "resolved": "1.2.0-beta.556",
+ "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==",
+ "dependencies": {
+ "StyleCop.Analyzers.Unstable": "1.2.0.556"
+ }
+ },
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
@@ -34,6 +43,11 @@
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
+ "StyleCop.Analyzers.Unstable": {
+ "type": "Transitive",
+ "resolved": "1.2.0.556",
+ "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ=="
+ },
"singlestoreconnector": {
"type": "Project",
"dependencies": {
@@ -63,18 +77,18 @@
}
}
},
- "net9.0": {
+ "net8.0": {
"Microsoft.DotNet.ILCompiler": {
"type": "Direct",
- "requested": "[9.0.4, )",
- "resolved": "9.0.4",
- "contentHash": "G85txKEuQ8s64BG9Pk3TbmE+cDgKReepnIPNfC1lNks4u2v5SkQhjCuuSAep+H2xtgFpYU7w9LFOF+vVtJZguA=="
+ "requested": "[8.0.26, )",
+ "resolved": "8.0.26",
+ "contentHash": "TopkZJbGfDfqNRQatno3/UZh8t/mo4tlglGVtcUAZDEplx/DhmBUSoT7UM7M6W0/ah1Wbe6e46FOLDyHL8K4nQ=="
},
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
- "requested": "[9.0.4, )",
- "resolved": "9.0.4",
- "contentHash": "xUdlUxiFwXhTYhB4VxKg/IA0+jlZXJPo70LYuMryWbJHdonIpZjw+7DO2B0pWwpXIOs6MlH5WVXPEtfrGEcVZA=="
+ "requested": "[8.0.26, )",
+ "resolved": "8.0.26",
+ "contentHash": "o7/yVssM2r9Wyln2s9edBd5ANZXqdSdBI+g7JqXkyJmXrhs2WsJp25K5yPnYrTgdKBCjKB8bg+O2oew4sgzFaA=="
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
@@ -86,6 +100,15 @@
"Microsoft.SourceLink.Common": "8.0.0"
}
},
+ "StyleCop.Analyzers": {
+ "type": "Direct",
+ "requested": "[1.2.0-beta.556, )",
+ "resolved": "1.2.0-beta.556",
+ "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==",
+ "dependencies": {
+ "StyleCop.Analyzers.Unstable": "1.2.0.556"
+ }
+ },
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
@@ -96,6 +119,11 @@
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
+ "StyleCop.Analyzers.Unstable": {
+ "type": "Transitive",
+ "resolved": "1.2.0.556",
+ "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ=="
+ },
"singlestoreconnector": {
"type": "Project",
"dependencies": {
diff --git a/tests/SingleStoreConnector.Tests/ByteBufferWriterTests.cs b/tests/SingleStoreConnector.Tests/ByteBufferWriterTests.cs
index 0a7ca1c76..d30590e76 100644
--- a/tests/SingleStoreConnector.Tests/ByteBufferWriterTests.cs
+++ b/tests/SingleStoreConnector.Tests/ByteBufferWriterTests.cs
@@ -22,6 +22,6 @@ public void WriteString(int length)
var input = char.ConvertFromUtf32(0xE001);
for (var i = 0; i < length; i++)
writer.Write(input);
- Assert.Equal(expected, writer.ArraySegment);
+ Assert.Equal(expected, writer.ArraySegment.ToArray());
}
}
diff --git a/tests/SingleStoreConnector.Tests/CachedProcedureTests.cs b/tests/SingleStoreConnector.Tests/CachedProcedureTests.cs
index 01e46ea6a..1430442dd 100644
--- a/tests/SingleStoreConnector.Tests/CachedProcedureTests.cs
+++ b/tests/SingleStoreConnector.Tests/CachedProcedureTests.cs
@@ -4,9 +4,9 @@ public class CachedProcedureTests
{
[Theory]
[MemberData(nameof(CreateParseableParameters))]
- public void ParseParameters(string sql, object[] expected)
+ public void ParseParameters(string sql, SingleStoreGuidFormat guidFormat, object[] expected)
{
- var actual = CachedProcedure.ParseParameters(sql);
+ var actual = CachedProcedure.ParseParameters(sql, guidFormat);
Assert.Equal(expected.Length, actual.Count);
for (int i = 0; i < expected.Length; i++)
{
@@ -25,106 +25,118 @@ public static IEnumerable CreateParseableParameters()
{
new object[]
{
- "", new object[0],
+ "", SingleStoreGuidFormat.Binary16, new object[0],
},
[
- "/* no, parameters */", new object[0],
+ "/* no, parameters */", SingleStoreGuidFormat.Binary16, new object[0],
],
[
- "IN test INT", new object[]
+ "IN test INT", SingleStoreGuidFormat.Binary16, new object[]
{
- new CachedParameter(1, "IN", "test", "INT", false, 0),
+ new CachedParameter(1, "IN", "test", "INT", false, 0, SingleStoreGuidFormat.Binary16),
}
],
[
- "IN test INT UNSIGNED", new object[]
+ "IN test INT UNSIGNED", SingleStoreGuidFormat.Binary16, new object[]
{
- new CachedParameter(1, "IN", "test", "INT", true, 0),
+ new CachedParameter(1, "IN", "test", "INT", true, 0, SingleStoreGuidFormat.Binary16),
}
],
[
- "-- IN ignored INT UNSIGNED,\r\nIN notignored INT", new object[]
+ "-- IN ignored INT UNSIGNED,\r\nIN notignored INT", SingleStoreGuidFormat.Binary16, new object[]
{
- new CachedParameter(1, "IN", "notignored", "INT", false, 0),
+ new CachedParameter(1, "IN", "notignored", "INT", false, 0, SingleStoreGuidFormat.Binary16),
}
],
[
- "IN param1 INT,\r\nIN param2 INT", new object[]
+ "IN param1 INT,\r\nIN param2 INT", SingleStoreGuidFormat.Binary16, new object[]
{
- new CachedParameter(1, "IN", "param1", "INT", false, 0),
- new CachedParameter(2, "IN", "param2", "INT", false, 0),
+ new CachedParameter(1, "IN", "param1", "INT", false, 0, SingleStoreGuidFormat.Binary16),
+ new CachedParameter(2, "IN", "param2", "INT", false, 0, SingleStoreGuidFormat.Binary16),
}
],
[
- "IN /* ignored BIGINT,\r\nIN*/ param1 INT", new object[]
+ "IN /* ignored BIGINT,\r\nIN*/ param1 INT", SingleStoreGuidFormat.Binary16, new object[]
{
- new CachedParameter(1, "IN", "param1", "INT", false, 0),
+ new CachedParameter(1, "IN", "param1", "INT", false, 0, SingleStoreGuidFormat.Binary16),
}
],
[
- "IN param1 INT(11)", new object[]
+ "IN param1 INT(11)", SingleStoreGuidFormat.Binary16, new object[]
{
- new CachedParameter(1, "IN", "param1", "INT", false, 11),
+ new CachedParameter(1, "IN", "param1", "INT", false, 11, SingleStoreGuidFormat.Binary16),
}
],
[
- "param1 BIGINT(21) UNSIGNED ZEROFILL", new object[]
+ "param1 BIGINT(21) UNSIGNED ZEROFILL", SingleStoreGuidFormat.Binary16, new object[]
{
- new CachedParameter(1, "IN", "param1", "BIGINT", true, 21),
+ new CachedParameter(1, "IN", "param1", "BIGINT", true, 21, SingleStoreGuidFormat.Binary16),
}
],
[
- "param1 VARCHAR(63)", new object[]
+ "param1 VARCHAR(63)", SingleStoreGuidFormat.Binary16, new object[]
{
- new CachedParameter(1, "IN", "param1", "VARCHAR", false, 63),
+ new CachedParameter(1, "IN", "param1", "VARCHAR", false, 63, SingleStoreGuidFormat.Binary16),
}
],
[
- "param1 VARCHAR(63) CHARSET latin1", new object[]
+ "param1 VARCHAR(63) CHARSET latin1", SingleStoreGuidFormat.Binary16, new object[]
{
- new CachedParameter(1, "IN", "param1", "VARCHAR", false, 63),
+ new CachedParameter(1, "IN", "param1", "VARCHAR", false, 63, SingleStoreGuidFormat.Binary16),
}
],
[
- "param1 VARCHAR(63) COLLATE utf8bin", new object[]
+ "param1 VARCHAR(63) COLLATE utf8bin", SingleStoreGuidFormat.Binary16, new object[]
{
- new CachedParameter(1, "IN", "param1", "VARCHAR", false, 63),
+ new CachedParameter(1, "IN", "param1", "VARCHAR", false, 63, SingleStoreGuidFormat.Binary16),
}
],
[
- "param1 VARCHAR(63) CHARACTER SET latin1 COLLATE latin1_bin", new object[]
+ "param1 VARCHAR(63) CHARACTER SET latin1 COLLATE latin1_bin", SingleStoreGuidFormat.Binary16, new object[]
{
- new CachedParameter(1, "IN", "param1", "VARCHAR", false, 63),
+ new CachedParameter(1, "IN", "param1", "VARCHAR", false, 63, SingleStoreGuidFormat.Binary16),
}
],
[
- "`par``am` INT", new object[]
+ "`par``am` INT", SingleStoreGuidFormat.Binary16, new object[]
{
- new CachedParameter(1, "IN", "par`am", "INT", false, 0),
+ new CachedParameter(1, "IN", "par`am", "INT", false, 0, SingleStoreGuidFormat.Binary16),
}
],
[
- "IN input enum ('One', 'Two', 'Three')", new object[]
+ "IN input enum ('One', 'Two', 'Three')", SingleStoreGuidFormat.Binary16, new object[]
{
- new CachedParameter(1, "IN", "input", "ENUM", false, 0),
+ new CachedParameter(1, "IN", "input", "ENUM", false, 0, SingleStoreGuidFormat.Binary16),
}
],
[
- "OUT param DECIMAL(10,5)", new object[]
+ "OUT param DECIMAL(10,5)", SingleStoreGuidFormat.Binary16, new object[]
{
- new CachedParameter(1, "OUT", "param", "DECIMAL", false, 0),
+ new CachedParameter(1, "OUT", "param", "DECIMAL", false, 0, SingleStoreGuidFormat.Binary16),
}
],
[
- "INOUT param LONGTEXT", new object[]
+ "INOUT param LONGTEXT", SingleStoreGuidFormat.Binary16, new object[]
{
- new CachedParameter(1, "INOUT", "param", "LONGTEXT", false, 0),
+ new CachedParameter(1, "INOUT", "param", "LONGTEXT", false, 0, SingleStoreGuidFormat.Binary16),
}
],
[
- "ColSet set('set1','set2','set3')", new object[]
+ "OUT param1 BINARY(16)", SingleStoreGuidFormat.Binary16, new object[]
{
- new CachedParameter(1, "IN", "ColSet", "SET", false, 0),
+ new CachedParameter(1, "OUT", "param1", "BINARY", false, 16, SingleStoreGuidFormat.Binary16),
+ }
+ ],
+ [
+ "OUT param1 CHAR(36)", SingleStoreGuidFormat.Char36, new object[]
+ {
+ new CachedParameter(1, "OUT", "param1", "CHAR", false, 36, SingleStoreGuidFormat.Char36),
+ }
+ ],
+ [
+ "ColSet set('set1','set2','set3')", SingleStoreGuidFormat.Binary16, new object[]
+ {
+ new CachedParameter(1, "IN", "ColSet", "SET", false, 0, SingleStoreGuidFormat.Binary16),
}
],
[
@@ -135,14 +147,14 @@ param3 DECIMAL(20,10),
inout param4 VARCHAR(63) CHARSET latin1,
param5 bigint(20) unsigned zerofill,
out param6 bool",
- new object[]
+ SingleStoreGuidFormat.Binary16, new object[]
{
- new CachedParameter(1, "IN", "param1", "DATETIME", false, 6),
- new CachedParameter(2, "OUT", "param2", "INT", false, 0),
- new CachedParameter(3, "IN", "param3", "DECIMAL", false, 0),
- new CachedParameter(4, "INOUT", "param4", "VARCHAR", false, 63),
- new CachedParameter(5, "IN", "param5", "BIGINT", true, 20),
- new CachedParameter(6, "OUT", "param6", "TINYINT", false, 1),
+ new CachedParameter(1, "IN", "param1", "DATETIME", false, 6, SingleStoreGuidFormat.Binary16),
+ new CachedParameter(2, "OUT", "param2", "INT", false, 0, SingleStoreGuidFormat.Binary16),
+ new CachedParameter(3, "IN", "param3", "DECIMAL", false, 0, SingleStoreGuidFormat.Binary16),
+ new CachedParameter(4, "INOUT", "param4", "VARCHAR", false, 63, SingleStoreGuidFormat.Binary16),
+ new CachedParameter(5, "IN", "param5", "BIGINT", true, 20, SingleStoreGuidFormat.Binary16),
+ new CachedParameter(6, "OUT", "param6", "TINYINT", false, 1, SingleStoreGuidFormat.Binary16),
}
],
[
@@ -153,12 +165,12 @@ param3 real(20,10),
-- ignored INT
param4 INTEGER(3)
",
- new object[]
+ SingleStoreGuidFormat.Binary16, new object[]
{
- new CachedParameter(1, "IN", "param1", "TINYINT", false, 1),
- new CachedParameter(2, "IN", "param2", "VARCHAR", false, 0),
- new CachedParameter(3, "IN", "param3", "DOUBLE", false, 20),
- new CachedParameter(4, "IN", "param4", "INT", false, 3),
+ new CachedParameter(1, "IN", "param1", "TINYINT", false, 1, SingleStoreGuidFormat.Binary16),
+ new CachedParameter(2, "IN", "param2", "VARCHAR", false, 0, SingleStoreGuidFormat.Binary16),
+ new CachedParameter(3, "IN", "param3", "DOUBLE", false, 20, SingleStoreGuidFormat.Binary16),
+ new CachedParameter(4, "IN", "param4", "INT", false, 3, SingleStoreGuidFormat.Binary16),
}
],
};
diff --git a/tests/SingleStoreConnector.Tests/CancellationTests.cs b/tests/SingleStoreConnector.Tests/CancellationTests.cs
index 340210a24..a2ff220d4 100644
--- a/tests/SingleStoreConnector.Tests/CancellationTests.cs
+++ b/tests/SingleStoreConnector.Tests/CancellationTests.cs
@@ -18,7 +18,7 @@ public CancellationTests()
public void Dispose() => m_server.Stop();
- // NOTE: Multiple nested classes in order to force tests to run in parallel against each other
+ //// NOTE: Multiple nested classes in order to force tests to run in parallel against each other
public class CancelWithCommandTimeout : CancellationTests
{
@@ -329,7 +329,7 @@ public void Test(int step, int method)
Assert.Null(ex.InnerException);
// connection is unusable
- Assert.Equal(ConnectionState.Closed, connection.State);
+ Assert.Equal(ConnectionState.Broken, connection.State);
}
}
@@ -351,7 +351,7 @@ public async Task Test(int step, int method)
Assert.IsType(ex.InnerException);
// connection is unusable
- Assert.Equal(ConnectionState.Closed, connection.State);
+ Assert.Equal(ConnectionState.Broken, connection.State);
}
}
@@ -374,7 +374,7 @@ public void Execute(int step, int method)
Assert.Null(ex.InnerException);
// connection is unusable
- Assert.Equal(ConnectionState.Closed, connection.State);
+ Assert.Equal(ConnectionState.Broken, connection.State);
}
[SkipCITheory]
@@ -394,7 +394,7 @@ public async Task ExecuteAsync(int step, int method)
Assert.IsType(ex.InnerException);
// connection is unusable
- Assert.Equal(ConnectionState.Closed, connection.State);
+ Assert.Equal(ConnectionState.Broken, connection.State);
}
}
@@ -425,8 +425,19 @@ public static IEnumerable GetAsyncMethodSteps()
private static int ExecuteScalar(SingleStoreCommand command) => (int) command.ExecuteScalar();
private static async Task ExecuteScalarAsync(SingleStoreCommand command, CancellationToken token) => (int) await command.ExecuteScalarAsync(token);
- private static int ExecuteNonQuery(SingleStoreCommand command) { command.ExecuteNonQuery(); return 0; }
- private static async Task ExecuteNonQueryAsync(SingleStoreCommand command, CancellationToken token) { await command.ExecuteNonQueryAsync(token); return 0; }
+
+ private static int ExecuteNonQuery(SingleStoreCommand command)
+ {
+ command.ExecuteNonQuery();
+ return 0;
+ }
+
+ private static async Task ExecuteNonQueryAsync(SingleStoreCommand command, CancellationToken token)
+ {
+ await command.ExecuteNonQueryAsync(token);
+ return 0;
+ }
+
private static int ExecuteReader(SingleStoreCommand command)
{
using var reader = command.ExecuteReader();
@@ -450,6 +461,6 @@ private static async Task ExecuteReaderAsync(SingleStoreCommand command, Ca
return value.Value;
}
- readonly FakeSingleStoreServer m_server;
- readonly SingleStoreConnectionStringBuilder m_csb;
+ private readonly FakeSingleStoreServer m_server;
+ private readonly SingleStoreConnectionStringBuilder m_csb;
}
diff --git a/tests/SingleStoreConnector.Tests/ConnectionTests.cs b/tests/SingleStoreConnector.Tests/ConnectionTests.cs
index c179b7a05..45da9d555 100644
--- a/tests/SingleStoreConnector.Tests/ConnectionTests.cs
+++ b/tests/SingleStoreConnector.Tests/ConnectionTests.cs
@@ -166,14 +166,14 @@ public void Ping()
}
[Fact]
- public void PingWhenClosed()
+ public void PingWhenReset()
{
using var connection = new SingleStoreConnection(m_csb.ConnectionString);
connection.Open();
Assert.Equal(ConnectionState.Open, connection.State);
- m_server.Stop();
+ m_server.Reset();
Assert.False(connection.Ping());
- Assert.Equal(ConnectionState.Closed, connection.State);
+ Assert.Equal(ConnectionState.Broken, connection.State);
}
[Fact]
@@ -277,6 +277,72 @@ public void ReplaceActiveReader()
connection.Close();
}
+ [Fact(Skip = "Resetting connection is not supported in SingleStore")]
+ public async Task ResetServerConnectionWhileOpen()
+ {
+ var csb = new SingleStoreConnectionStringBuilder(m_csb.ConnectionString)
+ {
+ MaximumPoolSize = 5,
+ ConnectionTimeout = 5,
+ };
+
+ List tasks = [];
+ using var barrier = new Barrier((int) csb.MaximumPoolSize);
+ for (var i = 0; i < csb.MaximumPoolSize - 1; i++)
+ {
+ var threadId = i;
+ tasks.Add(Task.Run(async () =>
+ {
+ using var connection = new SingleStoreConnection(csb.ConnectionString);
+ await connection.OpenAsync().ConfigureAwait(false);
+
+ barrier.SignalAndWait();
+ //// wait for reset
+ barrier.SignalAndWait();
+
+ switch (threadId % 3)
+ {
+ case 0:
+ {
+ using (var command = connection.CreateCommand())
+ {
+ command.CommandText = "SELECT 1;";
+ var exception = Assert.Throws(() => command.ExecuteScalar());
+ Assert.Equal("Failed to read the result set.", exception.Message);
+ }
+ break;
+ }
+ case 1:
+ {
+ // NOTE: duplicate of PingWhenReset, but included here for completeness
+ var ping = await connection.PingAsync().ConfigureAwait(false);
+ Assert.False(ping);
+ break;
+ }
+ case 2:
+ {
+ await Assert.ThrowsAsync(async () => await connection.ResetConnectionAsync().ConfigureAwait(false));
+ break;
+ }
+ }
+
+ Assert.Equal(ConnectionState.Broken, connection.State);
+
+ await connection.CloseAsync().ConfigureAwait(false);
+ Assert.Equal(ConnectionState.Closed, connection.State);
+
+ await connection.OpenAsync().ConfigureAwait(false);
+ Assert.Equal(ConnectionState.Open, connection.State);
+ }));
+ }
+
+ barrier.SignalAndWait();
+ m_server.Reset();
+ barrier.SignalAndWait();
+
+ await Task.WhenAll(tasks);
+ }
+
private static async Task WaitForConditionAsync(T expected, Func getValue)
{
var sw = Stopwatch.StartNew();
@@ -285,6 +351,6 @@ private static async Task WaitForConditionAsync(T expected, Func getValue)
Assert.Equal(expected, getValue());
}
- readonly FakeSingleStoreServer m_server;
- readonly SingleStoreConnectionStringBuilder m_csb;
+ private readonly FakeSingleStoreServer m_server;
+ private readonly SingleStoreConnectionStringBuilder m_csb;
}
diff --git a/tests/SingleStoreConnector.Tests/DbProviderFactoryTests.cs b/tests/SingleStoreConnector.Tests/DbProviderFactoryTests.cs
index 0ccca30d2..1e5990212 100644
--- a/tests/SingleStoreConnector.Tests/DbProviderFactoryTests.cs
+++ b/tests/SingleStoreConnector.Tests/DbProviderFactoryTests.cs
@@ -1,7 +1,3 @@
-#if BASELINE
-using SingleStoreConnectorFactory = MySql.Data.MySqlClient.MySqlClientFactory;
-#endif
-
namespace SingleStoreConnector.Tests;
public class DbProviderFactoryTests
diff --git a/tests/SingleStoreConnector.Tests/DummyEnum.cs b/tests/SingleStoreConnector.Tests/DummyEnum.cs
index aad29ff07..abbe4601e 100644
--- a/tests/SingleStoreConnector.Tests/DummyEnum.cs
+++ b/tests/SingleStoreConnector.Tests/DummyEnum.cs
@@ -3,7 +3,7 @@ namespace SingleStoreConnector.Tests;
internal enum DummyEnum
{
FirstValue,
- SecondValue
+ SecondValue,
}
internal enum DummyByteEnum : byte
diff --git a/tests/SingleStoreConnector.Tests/FakeSingleStoreServer.cs b/tests/SingleStoreConnector.Tests/FakeSingleStoreServer.cs
index 07ca3459c..112130740 100644
--- a/tests/SingleStoreConnector.Tests/FakeSingleStoreServer.cs
+++ b/tests/SingleStoreConnector.Tests/FakeSingleStoreServer.cs
@@ -21,6 +21,22 @@ public void Start()
m_tasks.Add(AcceptConnectionsAsync());
}
+ public void Reset()
+ {
+ m_cts.Cancel();
+ try
+ {
+ Task.WaitAll(m_tasks.Skip(1).ToArray());
+ }
+ catch (AggregateException)
+ {
+ }
+ m_connections.Clear();
+ m_tasks.Clear();
+ m_cts.Dispose();
+ m_cts = new();
+ }
+
public void Stop()
{
if (m_cts is not null)
@@ -81,10 +97,10 @@ private async Task AcceptConnectionsAsync()
}
}
- readonly object m_lock;
- readonly TcpListener m_tcpListener;
- readonly List m_connections;
- readonly List m_tasks;
- CancellationTokenSource m_cts;
- int m_activeConnections;
+ private readonly object m_lock;
+ private readonly TcpListener m_tcpListener;
+ private readonly List m_connections;
+ private readonly List m_tasks;
+ private CancellationTokenSource m_cts;
+ private int m_activeConnections;
}
diff --git a/tests/SingleStoreConnector.Tests/FakeSingleStoreServerConnection.cs b/tests/SingleStoreConnector.Tests/FakeSingleStoreServerConnection.cs
index 9803fdc51..3b3eabedd 100644
--- a/tests/SingleStoreConnector.Tests/FakeSingleStoreServerConnection.cs
+++ b/tests/SingleStoreConnector.Tests/FakeSingleStoreServerConnection.cs
@@ -312,6 +312,6 @@ private static void WriteError(BinaryWriter writer, string message = "An unknown
writer.WriteRaw(message);
}
- readonly FakeSingleStoreServer m_server;
- readonly int m_connectionId;
+ private readonly FakeSingleStoreServer m_server;
+ private readonly int m_connectionId;
}
diff --git a/tests/SingleStoreConnector.Tests/Metrics/ConnectionTimeTests.cs b/tests/SingleStoreConnector.Tests/Metrics/ConnectionTimeTests.cs
index e4eb6a02a..ae803986b 100644
--- a/tests/SingleStoreConnector.Tests/Metrics/ConnectionTimeTests.cs
+++ b/tests/SingleStoreConnector.Tests/Metrics/ConnectionTimeTests.cs
@@ -11,7 +11,7 @@ public async Task ConnectionTime()
await connection.OpenAsync();
var measurements = GetAndClearMeasurements("db.client.connections.create_time");
var time = Assert.Single(measurements);
- // adjusted the highest value for S2MS
+ //// adjusted the highest value for S2MS
Assert.InRange(time, 0, 1300);
}
@@ -26,7 +26,7 @@ public async Task ConnectionTimeWithDelay()
await connection.OpenAsync();
var measurements = GetAndClearMeasurements("db.client.connections.create_time");
var time = Assert.Single(measurements);
- // adjusted the highest value for S2MS
+ //// adjusted the highest value for S2MS
Assert.InRange(time, 1000, 2300);
}
diff --git a/tests/SingleStoreConnector.Tests/Metrics/IConnectionCreator.cs b/tests/SingleStoreConnector.Tests/Metrics/IConnectionCreator.cs
index b8974905e..c075b04e4 100644
--- a/tests/SingleStoreConnector.Tests/Metrics/IConnectionCreator.cs
+++ b/tests/SingleStoreConnector.Tests/Metrics/IConnectionCreator.cs
@@ -45,7 +45,10 @@ public SingleStoreConnection OpenConnection()
}
public string PoolName { get; }
- public void Dispose() { }
+
+ public void Dispose()
+ {
+ }
private readonly string m_connectionString;
}
diff --git a/tests/SingleStoreConnector.Tests/Metrics/MetricsTestsBase.cs b/tests/SingleStoreConnector.Tests/Metrics/MetricsTestsBase.cs
index 6ad5a24e7..c3870910f 100644
--- a/tests/SingleStoreConnector.Tests/Metrics/MetricsTestsBase.cs
+++ b/tests/SingleStoreConnector.Tests/Metrics/MetricsTestsBase.cs
@@ -20,7 +20,7 @@ public MetricsTestsBase()
{
if (instrument.Meter.Name == "SingleStoreConnector")
listener.EnableMeasurementEvents(instrument);
- }
+ },
};
m_meterListener.SetMeasurementEventCallback(OnMeasurementRecorded);
m_meterListener.SetMeasurementEventCallback(OnMeasurementRecorded);
@@ -124,7 +124,6 @@ private void OnMeasurementRecorded(Instrument instrument, double measurement, Re
return (poolName, state);
}
-
private readonly Dictionary m_measurements;
private readonly Dictionary> m_timeMeasurements;
private readonly MeterListener m_meterListener;
diff --git a/tests/SingleStoreConnector.Tests/SingleStoreConnectionStringBuilderTests.cs b/tests/SingleStoreConnector.Tests/SingleStoreConnectionStringBuilderTests.cs
index 473a38683..276d61521 100644
--- a/tests/SingleStoreConnector.Tests/SingleStoreConnectionStringBuilderTests.cs
+++ b/tests/SingleStoreConnector.Tests/SingleStoreConnectionStringBuilderTests.cs
@@ -160,7 +160,7 @@ public void ParseConnectionString()
"ssl mode=verifyca;" +
"tls version=Tls12, TLS v1.3;" +
"Uid=username;" +
- "useaffectedrows=true"
+ "useaffectedrows=true",
};
Assert.True(csb.AllowLoadLocalInfile);
Assert.True(csb.AllowPublicKeyRetrieval);
@@ -169,7 +169,7 @@ public void ParseConnectionString()
Assert.False(csb.AutoEnlist);
#if !BASELINE
Assert.Equal(-1, csb.CancellationTimeout);
- // Connector/NET treats "CertificateFile" (client certificate) and "SslCa" (server CA) as aliases
+ //// Connector/NET treats "CertificateFile" (client certificate) and "SslCa" (server CA) as aliases
Assert.Equal("file.pfx", csb.CertificateFile);
#endif
Assert.Equal("Pass2345", csb.CertificatePassword);
@@ -589,8 +589,6 @@ public void SpecialCharactersInPassword()
Password = "foo;=bar,baz",
};
Assert.Equal("Password=\"foo;=bar,baz\"", builder.ConnectionString, StringComparer.OrdinalIgnoreCase);
-#if !BASELINE // https://bugs.mysql.com/bug.php?id=111797
using var connection = new SingleStoreConnection(builder.ConnectionString);
-#endif
}
}
diff --git a/tests/SingleStoreConnector.Tests/SingleStoreConnector.Tests.csproj b/tests/SingleStoreConnector.Tests/SingleStoreConnector.Tests.csproj
index 283bacbe1..da7e6ed5b 100644
--- a/tests/SingleStoreConnector.Tests/SingleStoreConnector.Tests.csproj
+++ b/tests/SingleStoreConnector.Tests/SingleStoreConnector.Tests.csproj
@@ -1,7 +1,7 @@
- net481;net9.0
+ net481;net8.0;net10.0
@@ -15,7 +15,7 @@
true
..\..\SingleStoreConnector.snk
true
- 12.0
+ $(NoWarn);SA1021
diff --git a/tests/SingleStoreConnector.Tests/SingleStoreDateTimeTests.cs b/tests/SingleStoreConnector.Tests/SingleStoreDateTimeTests.cs
index d3b469baf..e4646cfd3 100644
--- a/tests/SingleStoreConnector.Tests/SingleStoreDateTimeTests.cs
+++ b/tests/SingleStoreConnector.Tests/SingleStoreDateTimeTests.cs
@@ -7,7 +7,7 @@ public class SingleStoreDateTimeTests
[Fact]
public void NewSingleStoreDateTimeIsNotValidDateTime()
{
- var msdt = new SingleStoreDateTime();
+ var msdt = default(SingleStoreDateTime);
Assert.False(msdt.IsValidDateTime);
}
@@ -52,7 +52,7 @@ public void GetDateTime()
[Fact]
public void GetDateTimeForInvalidDate()
{
- var msdt = new SingleStoreDateTime();
+ var msdt = default(SingleStoreDateTime);
Assert.False(msdt.IsValidDateTime);
Assert.Throws(() => msdt.GetDateTime());
}
@@ -60,7 +60,7 @@ public void GetDateTimeForInvalidDate()
[Fact]
public void SetMicrosecond()
{
- var msdt = new SingleStoreDateTime();
+ var msdt = default(SingleStoreDateTime);
Assert.Equal(0, msdt.Microsecond);
msdt.Microsecond = 123456;
Assert.Equal(123, msdt.Millisecond);
@@ -93,7 +93,7 @@ public void ChangeTypeToDateTime()
[Fact]
public void NotConvertibleToDateTime()
{
- IConvertible convertible = new SingleStoreDateTime();
+ IConvertible convertible = default(SingleStoreDateTime);
#if !BASELINE
Assert.Throws(() => convertible.ToDateTime(CultureInfo.InvariantCulture));
#else
@@ -104,7 +104,7 @@ public void NotConvertibleToDateTime()
[Fact]
public void NotConvertToDateTime()
{
- object obj = new SingleStoreDateTime();
+ object obj = default(SingleStoreDateTime);
#if !BASELINE
Assert.Throws(() => Convert.ToDateTime(obj));
#else
@@ -115,7 +115,7 @@ public void NotConvertToDateTime()
[Fact]
public void NotChangeTypeToDateTime()
{
- object obj = new SingleStoreDateTime();
+ object obj = default(SingleStoreDateTime);
#if !BASELINE
Assert.Throws(() => Convert.ChangeType(obj, TypeCode.DateTime));
#else
@@ -134,7 +134,7 @@ public void ValidDateTimeConvertibleToString()
[Fact]
public void InvalidDateTimeConvertibleToString()
{
- IConvertible convertible = new SingleStoreDateTime();
+ IConvertible convertible = default(SingleStoreDateTime);
Assert.Equal("0000-00-00", convertible.ToString(CultureInfo.InvariantCulture));
}
#endif
@@ -255,6 +255,6 @@ public void Equal()
}
#endif
- static readonly SingleStoreDateTime s_mySqlDateTime = new(2018, 6, 9, 12, 34, 56, 123456);
- static readonly DateTime s_dateTime = new DateTime(2018, 6, 9, 12, 34, 56, 123).AddTicks(4560);
+ private static readonly SingleStoreDateTime s_mySqlDateTime = new(2018, 6, 9, 12, 34, 56, 123456);
+ private static readonly DateTime s_dateTime = new DateTime(2018, 6, 9, 12, 34, 56, 123).AddTicks(4560);
}
diff --git a/tests/SingleStoreConnector.Tests/SingleStoreParameterCollectionNameToIndexTests.cs b/tests/SingleStoreConnector.Tests/SingleStoreParameterCollectionNameToIndexTests.cs
index 2f84504d3..b016a546a 100644
--- a/tests/SingleStoreConnector.Tests/SingleStoreParameterCollectionNameToIndexTests.cs
+++ b/tests/SingleStoreConnector.Tests/SingleStoreParameterCollectionNameToIndexTests.cs
@@ -124,5 +124,5 @@ public void ChangeParameter()
Assert.Equal(1, m_collection.NormalizedIndexOf("D"));
}
- readonly SingleStoreParameterCollection m_collection;
+ private readonly SingleStoreParameterCollection m_collection;
}
diff --git a/tests/SingleStoreConnector.Tests/SingleStoreParameterCollectionTests.cs b/tests/SingleStoreConnector.Tests/SingleStoreParameterCollectionTests.cs
index 2e142cd34..3c172cf82 100644
--- a/tests/SingleStoreConnector.Tests/SingleStoreParameterCollectionTests.cs
+++ b/tests/SingleStoreConnector.Tests/SingleStoreParameterCollectionTests.cs
@@ -19,5 +19,5 @@ public SingleStoreParameterCollectionTests()
[Fact]
public void RemoveAtEnd() => Assert.Throws(() => m_collection.RemoveAt(0));
- readonly SingleStoreParameterCollection m_collection;
+ private readonly SingleStoreParameterCollection m_collection;
}
diff --git a/tests/SingleStoreConnector.Tests/SingleStoreParameterTests.cs b/tests/SingleStoreConnector.Tests/SingleStoreParameterTests.cs
index 8eb63cbdd..4a6210c5e 100644
--- a/tests/SingleStoreConnector.Tests/SingleStoreParameterTests.cs
+++ b/tests/SingleStoreConnector.Tests/SingleStoreParameterTests.cs
@@ -1,9 +1,9 @@
using System;
using System.Data;
using System.Text;
+using SingleStoreConnector;
using SingleStoreConnector.Core;
using SingleStoreConnector.Protocol.Serialization;
-using SingleStoreConnector;
using Xunit;
namespace SingleStoreConnector.Tests;
@@ -20,14 +20,14 @@ private string EncodeParameterToAscii(SingleStoreParameter parameter, StatementP
[Fact]
public void ZeroByteInBinary()
{
- var parameter = new SingleStoreParameter {Direction = ParameterDirection.Input, Value = new byte[]{0x54, 0x00, 0x45, 0x53, 0x54}};
+ var parameter = new SingleStoreParameter { Direction = ParameterDirection.Input, Value = new byte[] { 0x54, 0x00, 0x45, 0x53, 0x54 } };
Assert.Equal(@"_binary'T\0EST'", EncodeParameterToAscii(parameter));
}
[Fact]
public void ZeroByteInGuid()
{
- var parameter = new SingleStoreParameter {Direction = ParameterDirection.Input, Value = new Guid(new byte[]{0x44, 0x49, 0x55, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})};
+ var parameter = new SingleStoreParameter { Direction = ParameterDirection.Input, Value = new Guid(new byte[] { 0x44, 0x49, 0x55, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }) };
Assert.Equal(@"_binary'GUID\0\0\0\0\0\0\0\0\0\0\0\0'", EncodeParameterToAscii(parameter, StatementPreparerOptions.GuidFormatBinary16));
}
}
diff --git a/tests/SingleStoreConnector.Tests/StatementPreparerTests.cs b/tests/SingleStoreConnector.Tests/StatementPreparerTests.cs
index 9a543ff96..a5b34e126 100644
--- a/tests/SingleStoreConnector.Tests/StatementPreparerTests.cs
+++ b/tests/SingleStoreConnector.Tests/StatementPreparerTests.cs
@@ -209,7 +209,6 @@ [new MemoryStream([0, 0x41, 0x42, 0x27, 0x61, 0x5c, 0x62, 0x63], 1, 6, false, tr
[new StringBuilder("\"AB\\ab'"), @"'""AB\\ab'''"],
};
-
[Theory]
[InlineData(StatementPreparerOptions.GuidFormatChar36, "'61626364-6566-6768-696a-6b6c6d6e6f70'")]
[InlineData(StatementPreparerOptions.GuidFormatChar32, "'6162636465666768696a6b6c6d6e6f70'")]
diff --git a/tests/SingleStoreConnector.Tests/TypeMapperTests.cs b/tests/SingleStoreConnector.Tests/TypeMapperTests.cs
index d17f4acc8..f84397582 100644
--- a/tests/SingleStoreConnector.Tests/TypeMapperTests.cs
+++ b/tests/SingleStoreConnector.Tests/TypeMapperTests.cs
@@ -36,13 +36,13 @@ public void DbTypeMappingTest(Type clrType, DbType dbType)
[InlineData((byte) 1, DbType.Int16, (short) 1)]
[InlineData((short) 1, DbType.UInt16, (ushort) 1)]
[InlineData((ushort) 1, DbType.Int32, 1)]
- [InlineData(1, DbType.UInt32, (uint) 1)]
- [InlineData((uint) 1, DbType.Int64, (long) 1)]
- [InlineData((long) 1, DbType.UInt64, (ulong) 1)]
- [InlineData((ulong) 1, DbType.String, "1")]
- [InlineData((ulong) 1, DbType.StringFixedLength, "1")]
- [InlineData((ulong) 1, DbType.AnsiString, "1")]
- [InlineData((ulong) 1, DbType.AnsiStringFixedLength, "1")]
+ [InlineData(1, DbType.UInt32, 1U)]
+ [InlineData(1U, DbType.Int64, 1L)]
+ [InlineData(1L, DbType.UInt64, 1UL)]
+ [InlineData(1UL, DbType.String, "1")]
+ [InlineData(1UL, DbType.StringFixedLength, "1")]
+ [InlineData(1UL, DbType.AnsiString, "1")]
+ [InlineData(1UL, DbType.AnsiStringFixedLength, "1")]
public void ConversionTest(object original, DbType dbType, object expected)
{
Assert.Equal(expected, TypeMapper.Instance.GetDbTypeMapping(dbType).DoConversion(original));
diff --git a/tests/SingleStoreConnector.Tests/UtilityTests.cs b/tests/SingleStoreConnector.Tests/UtilityTests.cs
index 18b85b818..eceae370d 100644
--- a/tests/SingleStoreConnector.Tests/UtilityTests.cs
+++ b/tests/SingleStoreConnector.Tests/UtilityTests.cs
@@ -83,14 +83,14 @@ public void ParseTimeSpanFails(string input)
[Theory]
[InlineData("", "")]
- [InlineData("pre", "")]
- [InlineData("", "post")]
- [InlineData("pre", "post")]
+ [InlineData("pre\n", "")]
+ [InlineData("", "\npost")]
+ [InlineData("pre\n", "\npost")]
public void DecodePublicKey(string pre, string post)
{
#if NET5_0_OR_GREATER
using var rsa = RSA.Create();
- Utility.LoadRsaParameters(pre + c_publicKey + post, rsa);
+ Utility.LoadRsaParameters(Encoding.ASCII.GetBytes(pre + c_publicKey + post), rsa);
var parameters = rsa.ExportParameters(false);
#else
var parameters = Utility.GetRsaParameters(pre + c_publicKey + post);
@@ -101,14 +101,14 @@ public void DecodePublicKey(string pre, string post)
[Theory]
[InlineData("", "")]
- [InlineData("pre", "")]
- [InlineData("", "post")]
- [InlineData("pre", "post")]
+ [InlineData("pre\n", "")]
+ [InlineData("", "\npost")]
+ [InlineData("pre\n", "\npost")]
public void DecodePrivateKey(string pre, string post)
{
#if NET5_0_OR_GREATER
using var rsa = RSA.Create();
- Utility.LoadRsaParameters(pre + c_privateKey + post, rsa);
+ Utility.LoadRsaParameters(Encoding.ASCII.GetBytes(pre + c_privateKey + post), rsa);
var parameters = rsa.ExportParameters(true);
#else
var parameters = Utility.GetRsaParameters(pre + c_privateKey + post);
@@ -123,7 +123,7 @@ public void DecodePrivateKey(string pre, string post)
Assert.Equal(s_iq, parameters.InverseQ);
}
- const string c_publicKey = @"-----BEGIN PUBLIC KEY-----
+ private const string c_publicKey = @"-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwmlDX62hQAQNvSJZ/HAO
UjCbAiEPQquEyPpxjqDxyx1fVxL93U1au50xGk4sad4OH+GSZCChqj3kvwJhXc52
iHdBzjQbucGYlC1wLNMc1F+H89vMjEq1ZexsRDWQSrgL1I6i9Mn5NFgS563yPBpO
@@ -133,7 +133,7 @@ public void DecodePrivateKey(string pre, string post)
LwIDAQAB
-----END PUBLIC KEY-----";
- const string c_privateKey = @"-----BEGIN RSA PRIVATE KEY-----
+ private const string c_privateKey = @"-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwmlDX62hQAQNvSJZ/HAOUjCbAiEPQquEyPpxjqDxyx1fVxL9
3U1au50xGk4sad4OH+GSZCChqj3kvwJhXc52iHdBzjQbucGYlC1wLNMc1F+H89vM
jEq1ZexsRDWQSrgL1I6i9Mn5NFgS563yPBpOyfYyGWCrL5w7yI+we3MCwy2q8JIO
@@ -161,12 +161,12 @@ public void DecodePrivateKey(string pre, string post)
joMn7CFmwV9EYjPTxwkByKs5a8AbNYJDSfNCa3KoHTFjOvVMPec90Q==
-----END RSA PRIVATE KEY-----";
- static readonly byte[] s_modulus = [0xC2, 0x69, 0x43, 0x5F, 0xAD, 0xA1, 0x40, 0x04, 0x0D, 0xBD, 0x22, 0x59, 0xFC, 0x70, 0x0E, 0x52, 0x30, 0x9B, 0x02, 0x21, 0x0F, 0x42, 0xAB, 0x84, 0xC8, 0xFA, 0x71, 0x8E, 0xA0, 0xF1, 0xCB, 0x1D, 0x5F, 0x57, 0x12, 0xFD, 0xDD, 0x4D, 0x5A, 0xBB, 0x9D, 0x31, 0x1A, 0x4E, 0x2C, 0x69, 0xDE, 0x0E, 0x1F, 0xE1, 0x92, 0x64, 0x20, 0xA1, 0xAA, 0x3D, 0xE4, 0xBF, 0x02, 0x61, 0x5D, 0xCE, 0x76, 0x88, 0x77, 0x41, 0xCE, 0x34, 0x1B, 0xB9, 0xC1, 0x98, 0x94, 0x2D, 0x70, 0x2C, 0xD3, 0x1C, 0xD4, 0x5F, 0x87, 0xF3, 0xDB, 0xCC, 0x8C, 0x4A, 0xB5, 0x65, 0xEC, 0x6C, 0x44, 0x35, 0x90, 0x4A, 0xB8, 0x0B, 0xD4, 0x8E, 0xA2, 0xF4, 0xC9, 0xF9, 0x34, 0x58, 0x12, 0xE7, 0xAD, 0xF2, 0x3C, 0x1A, 0x4E, 0xC9, 0xF6, 0x32, 0x19, 0x60, 0xAB, 0x2F, 0x9C, 0x3B, 0xC8, 0x8F, 0xB0, 0x7B, 0x73, 0x02, 0xC3, 0x2D, 0xAA, 0xF0, 0x92, 0x0E, 0xC9, 0x97, 0xA0, 0xCE, 0x1E, 0xFA, 0x5B, 0xE7, 0xF7, 0x17, 0xDA, 0x45, 0x76, 0x77, 0xE3, 0x40, 0xEF, 0xAF, 0x8C, 0xFC, 0x8D, 0xBA, 0x99, 0xD3, 0x10, 0x3B, 0x58, 0x6C, 0x68, 0xC5, 0x61, 0xAA, 0x98, 0x21, 0x33, 0x67, 0x71, 0x9F, 0x1C, 0x2F, 0x52, 0xD8, 0x57, 0x0B, 0x94, 0x5D, 0x6D, 0x0F, 0x5B, 0xC6, 0xDF, 0x94, 0x47, 0x45, 0x77, 0xF0, 0x6B, 0xF1, 0xFC, 0xC2, 0x80, 0xAE, 0x08, 0x55, 0xD4, 0xA6, 0xE8, 0xE5, 0x4F, 0xCD, 0x26, 0x5A, 0xB5, 0xAC, 0x38, 0xDA, 0x37, 0x17, 0xDA, 0xD3, 0x53, 0xF1, 0xFD, 0xCF, 0xDC, 0x29, 0x40, 0xBC, 0xBD, 0x55, 0xDE, 0x3E, 0x04, 0x74, 0x21, 0xF2, 0x60, 0xDA, 0xCA, 0x26, 0xAC, 0x19, 0xEE, 0x74, 0xED, 0x8E, 0xA0, 0x41, 0x3E, 0xA3, 0x66, 0x36, 0x27, 0x11, 0xDD, 0xE3, 0x3C, 0xB2, 0xE1, 0xA3, 0x31, 0x6C, 0xA6, 0x2F];
- static readonly byte[] s_exponent = [0x01, 0x00, 0x01];
- static readonly byte[] s_d = [0x11, 0xB3, 0x61, 0xD6, 0x01, 0x4A, 0x10, 0x39, 0x0E, 0x43, 0x2D, 0x30, 0x82, 0x42, 0x75, 0x9B, 0x58, 0x1F, 0x94, 0xE7, 0x0B, 0xAB, 0xA7, 0x50, 0x34, 0xB8, 0x50, 0xC4, 0x8A, 0xB4, 0xD9, 0x28, 0x78, 0x78, 0xC6, 0x1A, 0xE5, 0x1E, 0x58, 0xE7, 0x82, 0x1D, 0x69, 0x66, 0xBA, 0xB6, 0x7C, 0xE8, 0x4E, 0x50, 0xCC, 0x72, 0x5F, 0x62, 0x38, 0xCC, 0xDF, 0xD9, 0xE0, 0x4E, 0x9B, 0x2A, 0xE4, 0x31, 0xF2, 0xA1, 0xC4, 0x33, 0x8C, 0x5E, 0xB2, 0x58, 0xC1, 0x0D, 0x0E, 0x0C, 0x96, 0xC0, 0x26, 0x39, 0xF8, 0x77, 0xE4, 0x88, 0x50, 0x31, 0xB1, 0x28, 0x71, 0x89, 0x3E, 0x00, 0x9E, 0x7D, 0x9F, 0x1A, 0x3C, 0xA5, 0x2C, 0x03, 0x1B, 0xAC, 0x9B, 0xEE, 0x74, 0xF2, 0x31, 0x2D, 0x3F, 0xF9, 0xA9, 0x38, 0xCC, 0x38, 0xAD, 0x84, 0xD6, 0xB1, 0x44, 0x09, 0x51, 0x6E, 0x7B, 0xC4, 0x11, 0xA9, 0xAB, 0xB9, 0x80, 0xD4, 0xA8, 0x87, 0xEF, 0x2C, 0x76, 0x51, 0x64, 0x3E, 0xE5, 0x37, 0x7F, 0xF1, 0x26, 0xD7, 0xC0, 0x80, 0xEB, 0x96, 0xA9, 0x1A, 0x4D, 0xB8, 0xAF, 0xC9, 0xE5, 0xA2, 0x43, 0x7A, 0x6C, 0xEB, 0x9F, 0xF3, 0x8A, 0x72, 0xDD, 0xE7, 0xD2, 0xEB, 0x60, 0xD6, 0x01, 0xDD, 0x25, 0x40, 0x8B, 0x71, 0xBA, 0x0D, 0x49, 0x55, 0x3A, 0x1D, 0x9B, 0x60, 0x71, 0x2C, 0x37, 0xAF, 0xFB, 0xD9, 0x3C, 0xEF, 0x77, 0xF1, 0x43, 0xE9, 0x7F, 0xE9, 0x2C, 0xDC, 0x76, 0xDC, 0x34, 0x48, 0xFF, 0x4F, 0xB4, 0xBE, 0x93, 0x82, 0xC3, 0x14, 0x42, 0xC6, 0xE4, 0x01, 0xF5, 0xB0, 0xDE, 0xED, 0x64, 0x82, 0xB9, 0x1C, 0x17, 0x0A, 0x28, 0x89, 0x66, 0xC2, 0x45, 0xA3, 0x4E, 0xBC, 0x61, 0xB4, 0xD7, 0xCC, 0x47, 0xDC, 0x86, 0x8E, 0x1F, 0xEE, 0x8E, 0x0D, 0x0F, 0x51, 0xB1, 0xAC, 0x62, 0xEF, 0xDC, 0x6D, 0x90, 0x81];
- static readonly byte[] s_p = [0xFA, 0x63, 0x13, 0x19, 0xF3, 0x13, 0x7A, 0xC0, 0x70, 0x67, 0xAD, 0xE8, 0x05, 0x26, 0x70, 0x68, 0x9F, 0x30, 0xFB, 0x06, 0x9E, 0x45, 0xFF, 0x38, 0xF2, 0x56, 0xA3, 0x35, 0x4F, 0xAE, 0x21, 0x20, 0x94, 0x44, 0x80, 0x42, 0x61, 0x39, 0xC3, 0xEB, 0xA9, 0x8F, 0xD2, 0x61, 0x88, 0xE9, 0x6A, 0x62, 0x42, 0xF0, 0x18, 0x55, 0xDE, 0x50, 0x73, 0x1E, 0xFA, 0x70, 0xE5, 0x25, 0x73, 0x28, 0x6F, 0x29, 0x40, 0xE0, 0x09, 0x1A, 0xB6, 0x11, 0xF4, 0xB1, 0xEC, 0x89, 0x25, 0x66, 0x7D, 0x41, 0xC7, 0x53, 0xFA, 0x3A, 0x58, 0xF0, 0xB2, 0x27, 0x73, 0xE8, 0x63, 0x49, 0x06, 0x15, 0xF1, 0x4C, 0x35, 0x9D, 0x33, 0xE5, 0x28, 0x3E, 0xBF, 0x2F, 0x9F, 0xE4, 0x33, 0xE9, 0xCE, 0x6F, 0x91, 0x18, 0xAD, 0x95, 0xD4, 0xF5, 0x12, 0xD6, 0x92, 0xBD, 0xDA, 0xDF, 0x64, 0xA8, 0xB4, 0xEC, 0x7E, 0xD8, 0x1A, 0x41];
- static readonly byte[] s_q = [0xC6, 0xC4, 0xF4, 0x19, 0x1A, 0x74, 0xFB, 0xA1, 0x07, 0x3D, 0x8A, 0x80, 0x9A, 0x6F, 0xC5, 0x60, 0xCB, 0x68, 0x32, 0x14, 0xCA, 0x9D, 0x21, 0x59, 0x7C, 0xA5, 0xAC, 0x0B, 0x68, 0x0E, 0x8C, 0xE2, 0x82, 0x77, 0x6C, 0xBC, 0xC2, 0xA6, 0xB3, 0xF2, 0x8B, 0xE4, 0xC8, 0xDF, 0xF9, 0x7B, 0x92, 0x82, 0xE6, 0x20, 0x84, 0xCB, 0xE0, 0x46, 0x44, 0x0A, 0xB4, 0xD7, 0x48, 0x43, 0x29, 0x78, 0xE7, 0xCA, 0xE4, 0x8F, 0xEC, 0x52, 0x2A, 0x6F, 0xDA, 0x58, 0x8D, 0xAA, 0xEB, 0xC0, 0x5E, 0x4F, 0xC1, 0x70, 0x9A, 0x97, 0xCE, 0xE4, 0xFF, 0x70, 0xEC, 0xA1, 0x2A, 0xED, 0xD8, 0x7A, 0xA1, 0xA5, 0x1C, 0x79, 0x2A, 0x8E, 0xBC, 0x74, 0xA3, 0x45, 0x20, 0x27, 0x14, 0xE1, 0xB9, 0x16, 0x80, 0x26, 0x81, 0xC2, 0x17, 0x26, 0xBB, 0x83, 0x04, 0x88, 0xFB, 0x0B, 0xB2, 0x06, 0x49, 0x88, 0x9A, 0xC0, 0x44, 0x6F];
- static readonly byte[] s_dp = [0xA9, 0x1C, 0xD1, 0x81, 0xED, 0x53, 0x72, 0xCD, 0x17, 0x1E, 0x6F, 0xAF, 0x0E, 0x0B, 0x70, 0x50, 0xB9, 0x74, 0x73, 0x97, 0x7C, 0xBE, 0xFA, 0x2A, 0x94, 0x43, 0x3F, 0xE8, 0x79, 0xF0, 0xBA, 0x1F, 0x2F, 0x05, 0x35, 0x3B, 0xA0, 0x3F, 0x4B, 0xBC, 0x97, 0xC1, 0xB3, 0xEE, 0x7C, 0x72, 0x6A, 0x90, 0x03, 0x75, 0xF6, 0x79, 0xC8, 0xB5, 0xBD, 0x8B, 0x66, 0xCB, 0x69, 0x79, 0x69, 0xD6, 0x1D, 0x7E, 0x57, 0x7A, 0xF9, 0xAB, 0x38, 0xDD, 0xD7, 0xCF, 0x74, 0x5B, 0x0D, 0x0F, 0xB3, 0xFA, 0x01, 0x8F, 0x0E, 0xAA, 0xE9, 0xF7, 0x67, 0xDC, 0x8D, 0xC4, 0x9F, 0x8A, 0x6A, 0xB9, 0xD7, 0x48, 0x09, 0xCE, 0x2B, 0x86, 0xD3, 0x2F, 0xCE, 0x21, 0x74, 0x74, 0xC5, 0x3B, 0x5D, 0x5A, 0xAD, 0x3D, 0x65, 0xC0, 0x95, 0x1D, 0x11, 0xA4, 0x2B, 0x68, 0x5F, 0xB4, 0x12, 0x60, 0x80, 0x12, 0x5B, 0x79, 0xCA, 0x01];
- static readonly byte[] s_dq = [0x99, 0x7F, 0xF2, 0x0E, 0x0C, 0xDB, 0x78, 0x93, 0x63, 0x97, 0x08, 0x05, 0xC6, 0xBE, 0x38, 0x4C, 0x46, 0xE9, 0x21, 0x7B, 0xE2, 0xF8, 0xB3, 0x8F, 0x7A, 0xEE, 0x2A, 0x4D, 0xE8, 0xBF, 0x0B, 0xD7, 0xC4, 0xEF, 0x5B, 0x3E, 0xEE, 0x87, 0x74, 0x82, 0x03, 0xBC, 0xDB, 0xCF, 0xF3, 0xC7, 0x95, 0x5E, 0x97, 0x3F, 0x57, 0xAE, 0x66, 0x75, 0x7D, 0x08, 0x53, 0x9D, 0xC9, 0x85, 0x6C, 0x5A, 0x9D, 0x8E, 0x97, 0x31, 0xFA, 0x4B, 0x7D, 0xD0, 0x41, 0x6B, 0x8F, 0x84, 0x06, 0x69, 0xD9, 0xAB, 0x77, 0xF4, 0x70, 0xBE, 0x79, 0x9D, 0x69, 0x13, 0x18, 0xFA, 0x61, 0xBF, 0xAA, 0x25, 0x11, 0xFE, 0x03, 0x5E, 0x23, 0x1A, 0x75, 0xB9, 0x11, 0x79, 0x49, 0x9A, 0x44, 0xCB, 0x08, 0x77, 0xB2, 0xE1, 0x89, 0xE5, 0x8C, 0xA1, 0x71, 0xBF, 0x7D, 0x5A, 0x17, 0x9D, 0x71, 0x8E, 0xDF, 0x6C, 0xAA, 0x2D, 0x7E, 0xEB];
- static readonly byte[] s_iq = [0x2F, 0x91, 0x84, 0x58, 0xAB, 0x47, 0xAE, 0xE6, 0x92, 0xBC, 0xE4, 0x13, 0xBA, 0x9C, 0xDD, 0x66, 0xFF, 0x91, 0xA8, 0x94, 0x41, 0xBF, 0xD6, 0x54, 0x1B, 0x6D, 0x92, 0xC0, 0x67, 0xC6, 0x08, 0x23, 0xA4, 0x6D, 0x24, 0xA1, 0xC1, 0xBD, 0xCB, 0xC5, 0xAF, 0x4E, 0x96, 0x39, 0x46, 0x4A, 0x44, 0xD7, 0x10, 0x8F, 0x35, 0x76, 0xF5, 0xCB, 0x86, 0xE2, 0x9A, 0xAD, 0x32, 0xCD, 0x2C, 0xC0, 0xB8, 0x4A, 0x31, 0xB0, 0xAC, 0xF8, 0xFA, 0xAB, 0xEB, 0x0E, 0x8A, 0x7D, 0x92, 0x0D, 0xB5, 0x5C, 0x8B, 0x36, 0xD6, 0xC3, 0x56, 0x8F, 0x6B, 0xC0, 0x54, 0x5C, 0x8E, 0x83, 0x27, 0xEC, 0x21, 0x66, 0xC1, 0x5F, 0x44, 0x62, 0x33, 0xD3, 0xC7, 0x09, 0x01, 0xC8, 0xAB, 0x39, 0x6B, 0xC0, 0x1B, 0x35, 0x82, 0x43, 0x49, 0xF3, 0x42, 0x6B, 0x72, 0xA8, 0x1D, 0x31, 0x63, 0x3A, 0xF5, 0x4C, 0x3D, 0xE7, 0x3D, 0xD1];
+ private static readonly byte[] s_modulus = [0xC2, 0x69, 0x43, 0x5F, 0xAD, 0xA1, 0x40, 0x04, 0x0D, 0xBD, 0x22, 0x59, 0xFC, 0x70, 0x0E, 0x52, 0x30, 0x9B, 0x02, 0x21, 0x0F, 0x42, 0xAB, 0x84, 0xC8, 0xFA, 0x71, 0x8E, 0xA0, 0xF1, 0xCB, 0x1D, 0x5F, 0x57, 0x12, 0xFD, 0xDD, 0x4D, 0x5A, 0xBB, 0x9D, 0x31, 0x1A, 0x4E, 0x2C, 0x69, 0xDE, 0x0E, 0x1F, 0xE1, 0x92, 0x64, 0x20, 0xA1, 0xAA, 0x3D, 0xE4, 0xBF, 0x02, 0x61, 0x5D, 0xCE, 0x76, 0x88, 0x77, 0x41, 0xCE, 0x34, 0x1B, 0xB9, 0xC1, 0x98, 0x94, 0x2D, 0x70, 0x2C, 0xD3, 0x1C, 0xD4, 0x5F, 0x87, 0xF3, 0xDB, 0xCC, 0x8C, 0x4A, 0xB5, 0x65, 0xEC, 0x6C, 0x44, 0x35, 0x90, 0x4A, 0xB8, 0x0B, 0xD4, 0x8E, 0xA2, 0xF4, 0xC9, 0xF9, 0x34, 0x58, 0x12, 0xE7, 0xAD, 0xF2, 0x3C, 0x1A, 0x4E, 0xC9, 0xF6, 0x32, 0x19, 0x60, 0xAB, 0x2F, 0x9C, 0x3B, 0xC8, 0x8F, 0xB0, 0x7B, 0x73, 0x02, 0xC3, 0x2D, 0xAA, 0xF0, 0x92, 0x0E, 0xC9, 0x97, 0xA0, 0xCE, 0x1E, 0xFA, 0x5B, 0xE7, 0xF7, 0x17, 0xDA, 0x45, 0x76, 0x77, 0xE3, 0x40, 0xEF, 0xAF, 0x8C, 0xFC, 0x8D, 0xBA, 0x99, 0xD3, 0x10, 0x3B, 0x58, 0x6C, 0x68, 0xC5, 0x61, 0xAA, 0x98, 0x21, 0x33, 0x67, 0x71, 0x9F, 0x1C, 0x2F, 0x52, 0xD8, 0x57, 0x0B, 0x94, 0x5D, 0x6D, 0x0F, 0x5B, 0xC6, 0xDF, 0x94, 0x47, 0x45, 0x77, 0xF0, 0x6B, 0xF1, 0xFC, 0xC2, 0x80, 0xAE, 0x08, 0x55, 0xD4, 0xA6, 0xE8, 0xE5, 0x4F, 0xCD, 0x26, 0x5A, 0xB5, 0xAC, 0x38, 0xDA, 0x37, 0x17, 0xDA, 0xD3, 0x53, 0xF1, 0xFD, 0xCF, 0xDC, 0x29, 0x40, 0xBC, 0xBD, 0x55, 0xDE, 0x3E, 0x04, 0x74, 0x21, 0xF2, 0x60, 0xDA, 0xCA, 0x26, 0xAC, 0x19, 0xEE, 0x74, 0xED, 0x8E, 0xA0, 0x41, 0x3E, 0xA3, 0x66, 0x36, 0x27, 0x11, 0xDD, 0xE3, 0x3C, 0xB2, 0xE1, 0xA3, 0x31, 0x6C, 0xA6, 0x2F];
+ private static readonly byte[] s_exponent = [0x01, 0x00, 0x01];
+ private static readonly byte[] s_d = [0x11, 0xB3, 0x61, 0xD6, 0x01, 0x4A, 0x10, 0x39, 0x0E, 0x43, 0x2D, 0x30, 0x82, 0x42, 0x75, 0x9B, 0x58, 0x1F, 0x94, 0xE7, 0x0B, 0xAB, 0xA7, 0x50, 0x34, 0xB8, 0x50, 0xC4, 0x8A, 0xB4, 0xD9, 0x28, 0x78, 0x78, 0xC6, 0x1A, 0xE5, 0x1E, 0x58, 0xE7, 0x82, 0x1D, 0x69, 0x66, 0xBA, 0xB6, 0x7C, 0xE8, 0x4E, 0x50, 0xCC, 0x72, 0x5F, 0x62, 0x38, 0xCC, 0xDF, 0xD9, 0xE0, 0x4E, 0x9B, 0x2A, 0xE4, 0x31, 0xF2, 0xA1, 0xC4, 0x33, 0x8C, 0x5E, 0xB2, 0x58, 0xC1, 0x0D, 0x0E, 0x0C, 0x96, 0xC0, 0x26, 0x39, 0xF8, 0x77, 0xE4, 0x88, 0x50, 0x31, 0xB1, 0x28, 0x71, 0x89, 0x3E, 0x00, 0x9E, 0x7D, 0x9F, 0x1A, 0x3C, 0xA5, 0x2C, 0x03, 0x1B, 0xAC, 0x9B, 0xEE, 0x74, 0xF2, 0x31, 0x2D, 0x3F, 0xF9, 0xA9, 0x38, 0xCC, 0x38, 0xAD, 0x84, 0xD6, 0xB1, 0x44, 0x09, 0x51, 0x6E, 0x7B, 0xC4, 0x11, 0xA9, 0xAB, 0xB9, 0x80, 0xD4, 0xA8, 0x87, 0xEF, 0x2C, 0x76, 0x51, 0x64, 0x3E, 0xE5, 0x37, 0x7F, 0xF1, 0x26, 0xD7, 0xC0, 0x80, 0xEB, 0x96, 0xA9, 0x1A, 0x4D, 0xB8, 0xAF, 0xC9, 0xE5, 0xA2, 0x43, 0x7A, 0x6C, 0xEB, 0x9F, 0xF3, 0x8A, 0x72, 0xDD, 0xE7, 0xD2, 0xEB, 0x60, 0xD6, 0x01, 0xDD, 0x25, 0x40, 0x8B, 0x71, 0xBA, 0x0D, 0x49, 0x55, 0x3A, 0x1D, 0x9B, 0x60, 0x71, 0x2C, 0x37, 0xAF, 0xFB, 0xD9, 0x3C, 0xEF, 0x77, 0xF1, 0x43, 0xE9, 0x7F, 0xE9, 0x2C, 0xDC, 0x76, 0xDC, 0x34, 0x48, 0xFF, 0x4F, 0xB4, 0xBE, 0x93, 0x82, 0xC3, 0x14, 0x42, 0xC6, 0xE4, 0x01, 0xF5, 0xB0, 0xDE, 0xED, 0x64, 0x82, 0xB9, 0x1C, 0x17, 0x0A, 0x28, 0x89, 0x66, 0xC2, 0x45, 0xA3, 0x4E, 0xBC, 0x61, 0xB4, 0xD7, 0xCC, 0x47, 0xDC, 0x86, 0x8E, 0x1F, 0xEE, 0x8E, 0x0D, 0x0F, 0x51, 0xB1, 0xAC, 0x62, 0xEF, 0xDC, 0x6D, 0x90, 0x81];
+ private static readonly byte[] s_p = [0xFA, 0x63, 0x13, 0x19, 0xF3, 0x13, 0x7A, 0xC0, 0x70, 0x67, 0xAD, 0xE8, 0x05, 0x26, 0x70, 0x68, 0x9F, 0x30, 0xFB, 0x06, 0x9E, 0x45, 0xFF, 0x38, 0xF2, 0x56, 0xA3, 0x35, 0x4F, 0xAE, 0x21, 0x20, 0x94, 0x44, 0x80, 0x42, 0x61, 0x39, 0xC3, 0xEB, 0xA9, 0x8F, 0xD2, 0x61, 0x88, 0xE9, 0x6A, 0x62, 0x42, 0xF0, 0x18, 0x55, 0xDE, 0x50, 0x73, 0x1E, 0xFA, 0x70, 0xE5, 0x25, 0x73, 0x28, 0x6F, 0x29, 0x40, 0xE0, 0x09, 0x1A, 0xB6, 0x11, 0xF4, 0xB1, 0xEC, 0x89, 0x25, 0x66, 0x7D, 0x41, 0xC7, 0x53, 0xFA, 0x3A, 0x58, 0xF0, 0xB2, 0x27, 0x73, 0xE8, 0x63, 0x49, 0x06, 0x15, 0xF1, 0x4C, 0x35, 0x9D, 0x33, 0xE5, 0x28, 0x3E, 0xBF, 0x2F, 0x9F, 0xE4, 0x33, 0xE9, 0xCE, 0x6F, 0x91, 0x18, 0xAD, 0x95, 0xD4, 0xF5, 0x12, 0xD6, 0x92, 0xBD, 0xDA, 0xDF, 0x64, 0xA8, 0xB4, 0xEC, 0x7E, 0xD8, 0x1A, 0x41];
+ private static readonly byte[] s_q = [0xC6, 0xC4, 0xF4, 0x19, 0x1A, 0x74, 0xFB, 0xA1, 0x07, 0x3D, 0x8A, 0x80, 0x9A, 0x6F, 0xC5, 0x60, 0xCB, 0x68, 0x32, 0x14, 0xCA, 0x9D, 0x21, 0x59, 0x7C, 0xA5, 0xAC, 0x0B, 0x68, 0x0E, 0x8C, 0xE2, 0x82, 0x77, 0x6C, 0xBC, 0xC2, 0xA6, 0xB3, 0xF2, 0x8B, 0xE4, 0xC8, 0xDF, 0xF9, 0x7B, 0x92, 0x82, 0xE6, 0x20, 0x84, 0xCB, 0xE0, 0x46, 0x44, 0x0A, 0xB4, 0xD7, 0x48, 0x43, 0x29, 0x78, 0xE7, 0xCA, 0xE4, 0x8F, 0xEC, 0x52, 0x2A, 0x6F, 0xDA, 0x58, 0x8D, 0xAA, 0xEB, 0xC0, 0x5E, 0x4F, 0xC1, 0x70, 0x9A, 0x97, 0xCE, 0xE4, 0xFF, 0x70, 0xEC, 0xA1, 0x2A, 0xED, 0xD8, 0x7A, 0xA1, 0xA5, 0x1C, 0x79, 0x2A, 0x8E, 0xBC, 0x74, 0xA3, 0x45, 0x20, 0x27, 0x14, 0xE1, 0xB9, 0x16, 0x80, 0x26, 0x81, 0xC2, 0x17, 0x26, 0xBB, 0x83, 0x04, 0x88, 0xFB, 0x0B, 0xB2, 0x06, 0x49, 0x88, 0x9A, 0xC0, 0x44, 0x6F];
+ private static readonly byte[] s_dp = [0xA9, 0x1C, 0xD1, 0x81, 0xED, 0x53, 0x72, 0xCD, 0x17, 0x1E, 0x6F, 0xAF, 0x0E, 0x0B, 0x70, 0x50, 0xB9, 0x74, 0x73, 0x97, 0x7C, 0xBE, 0xFA, 0x2A, 0x94, 0x43, 0x3F, 0xE8, 0x79, 0xF0, 0xBA, 0x1F, 0x2F, 0x05, 0x35, 0x3B, 0xA0, 0x3F, 0x4B, 0xBC, 0x97, 0xC1, 0xB3, 0xEE, 0x7C, 0x72, 0x6A, 0x90, 0x03, 0x75, 0xF6, 0x79, 0xC8, 0xB5, 0xBD, 0x8B, 0x66, 0xCB, 0x69, 0x79, 0x69, 0xD6, 0x1D, 0x7E, 0x57, 0x7A, 0xF9, 0xAB, 0x38, 0xDD, 0xD7, 0xCF, 0x74, 0x5B, 0x0D, 0x0F, 0xB3, 0xFA, 0x01, 0x8F, 0x0E, 0xAA, 0xE9, 0xF7, 0x67, 0xDC, 0x8D, 0xC4, 0x9F, 0x8A, 0x6A, 0xB9, 0xD7, 0x48, 0x09, 0xCE, 0x2B, 0x86, 0xD3, 0x2F, 0xCE, 0x21, 0x74, 0x74, 0xC5, 0x3B, 0x5D, 0x5A, 0xAD, 0x3D, 0x65, 0xC0, 0x95, 0x1D, 0x11, 0xA4, 0x2B, 0x68, 0x5F, 0xB4, 0x12, 0x60, 0x80, 0x12, 0x5B, 0x79, 0xCA, 0x01];
+ private static readonly byte[] s_dq = [0x99, 0x7F, 0xF2, 0x0E, 0x0C, 0xDB, 0x78, 0x93, 0x63, 0x97, 0x08, 0x05, 0xC6, 0xBE, 0x38, 0x4C, 0x46, 0xE9, 0x21, 0x7B, 0xE2, 0xF8, 0xB3, 0x8F, 0x7A, 0xEE, 0x2A, 0x4D, 0xE8, 0xBF, 0x0B, 0xD7, 0xC4, 0xEF, 0x5B, 0x3E, 0xEE, 0x87, 0x74, 0x82, 0x03, 0xBC, 0xDB, 0xCF, 0xF3, 0xC7, 0x95, 0x5E, 0x97, 0x3F, 0x57, 0xAE, 0x66, 0x75, 0x7D, 0x08, 0x53, 0x9D, 0xC9, 0x85, 0x6C, 0x5A, 0x9D, 0x8E, 0x97, 0x31, 0xFA, 0x4B, 0x7D, 0xD0, 0x41, 0x6B, 0x8F, 0x84, 0x06, 0x69, 0xD9, 0xAB, 0x77, 0xF4, 0x70, 0xBE, 0x79, 0x9D, 0x69, 0x13, 0x18, 0xFA, 0x61, 0xBF, 0xAA, 0x25, 0x11, 0xFE, 0x03, 0x5E, 0x23, 0x1A, 0x75, 0xB9, 0x11, 0x79, 0x49, 0x9A, 0x44, 0xCB, 0x08, 0x77, 0xB2, 0xE1, 0x89, 0xE5, 0x8C, 0xA1, 0x71, 0xBF, 0x7D, 0x5A, 0x17, 0x9D, 0x71, 0x8E, 0xDF, 0x6C, 0xAA, 0x2D, 0x7E, 0xEB];
+ private static readonly byte[] s_iq = [0x2F, 0x91, 0x84, 0x58, 0xAB, 0x47, 0xAE, 0xE6, 0x92, 0xBC, 0xE4, 0x13, 0xBA, 0x9C, 0xDD, 0x66, 0xFF, 0x91, 0xA8, 0x94, 0x41, 0xBF, 0xD6, 0x54, 0x1B, 0x6D, 0x92, 0xC0, 0x67, 0xC6, 0x08, 0x23, 0xA4, 0x6D, 0x24, 0xA1, 0xC1, 0xBD, 0xCB, 0xC5, 0xAF, 0x4E, 0x96, 0x39, 0x46, 0x4A, 0x44, 0xD7, 0x10, 0x8F, 0x35, 0x76, 0xF5, 0xCB, 0x86, 0xE2, 0x9A, 0xAD, 0x32, 0xCD, 0x2C, 0xC0, 0xB8, 0x4A, 0x31, 0xB0, 0xAC, 0xF8, 0xFA, 0xAB, 0xEB, 0x0E, 0x8A, 0x7D, 0x92, 0x0D, 0xB5, 0x5C, 0x8B, 0x36, 0xD6, 0xC3, 0x56, 0x8F, 0x6B, 0xC0, 0x54, 0x5C, 0x8E, 0x83, 0x27, 0xEC, 0x21, 0x66, 0xC1, 0x5F, 0x44, 0x62, 0x33, 0xD3, 0xC7, 0x09, 0x01, 0xC8, 0xAB, 0x39, 0x6B, 0xC0, 0x1B, 0x35, 0x82, 0x43, 0x49, 0xF3, 0x42, 0x6B, 0x72, 0xA8, 0x1D, 0x31, 0x63, 0x3A, 0xF5, 0x4C, 0x3D, 0xE7, 0x3D, 0xD1];
}
diff --git a/tools/SchemaCollectionGenerator/SchemaCollectionGenerator.cs b/tools/SchemaCollectionGenerator/SchemaCollectionGenerator.cs
index 837c5ee2b..fc8d00e2a 100644
--- a/tools/SchemaCollectionGenerator/SchemaCollectionGenerator.cs
+++ b/tools/SchemaCollectionGenerator/SchemaCollectionGenerator.cs
@@ -25,12 +25,7 @@ internal sealed partial class SchemaProvider
{
public async ValueTask GetSchemaAsync(IOBehavior ioBehavior, string collectionName, string?[]? restrictionValues, CancellationToken cancellationToken)
{
- #if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(collectionName);
- #else
- if (collectionName is null)
- throw new ArgumentNullException(nameof(collectionName));
- #endif
var dataTable = new DataTable();
""");
diff --git a/tools/SchemaCollectionGenerator/SchemaCollectionGenerator.csproj b/tools/SchemaCollectionGenerator/SchemaCollectionGenerator.csproj
index 85c1ed799..cfbfc836c 100644
--- a/tools/SchemaCollectionGenerator/SchemaCollectionGenerator.csproj
+++ b/tools/SchemaCollectionGenerator/SchemaCollectionGenerator.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
true