diff --git a/.azuredevops/policies/approvercountpolicy.yml b/.azuredevops/policies/approvercountpolicy.yml
index 78e0c2aa0f..0a9c3868f9 100644
--- a/.azuredevops/policies/approvercountpolicy.yml
+++ b/.azuredevops/policies/approvercountpolicy.yml
@@ -3,10 +3,10 @@
# that govern how PRs are approved in general. The settings here dictate how
# the validator behaves, and it can also prevent PRs from completing.
#
-# Suggested by Merlinbot (https://sqlclientdrivers.visualstudio.com/ADO.Net/_git/dotnet-sqlclient/pullrequest/4982)
+# https://eng.ms/docs/coreai/devdiv/one-engineering-system-1es/1es-docs/policy-service/policy-as-code/approver-count-policy-overview
name: approver_count
-description: Approver count policy for dotnet-sqlclient
+description: Approver count policy for dotnet-sqlclient [internal/release/6.1]
resource: repository
where:
configuration:
@@ -22,8 +22,5 @@ configuration:
resetRejectionsOnSourcePush: false
blockLastPusherVote: true
branchNames:
- - refs/heads/internal/main
- - refs/heads/internal/release/6.0
- - refs/heads/internal/release/5.2
- - refs/heads/internal/release/5.1
- displayName: dotnet-sqlclient Approver Count Policy
+ - internal/release/6.1
+ displayName: dotnet-sqlclient Approver Count Policy [internal/release/6.1]
diff --git a/.editorconfig b/.editorconfig
index f0ea20ec32..ff6d9f3bd7 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -14,6 +14,9 @@ indent_size = 4
[*.{json,jsonc}]
indent_size = 2
+[*.{yml,yaml}]
+indent_size = 2
+
# C# files
[*.cs]
# New line preferences
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000000..ebf6a8cec5
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,98 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL Advanced"
+
+on:
+ push:
+ branches: [ "release/6.1" ]
+ pull_request:
+ branches: [ "release/6.1" ]
+ schedule:
+ - cron: '15 22 * * 6'
+
+jobs:
+ analyze:
+ name: Analyze (${{ matrix.language }})
+ # Runner size impacts CodeQL analysis time. To learn more, please see:
+ # - https://gh.io/recommended-hardware-resources-for-running-codeql
+ # - https://gh.io/supported-runners-and-hardware-resources
+ # - https://gh.io/using-larger-runners (GitHub.com only)
+ # Consider using larger runners or machines with greater resources for possible analysis time improvements.
+ runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
+ permissions:
+ # required for all workflows
+ security-events: write
+
+ # required to fetch internal or private CodeQL packs
+ packages: read
+
+ # only required for workflows in private repositories
+ actions: read
+ contents: read
+
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - language: csharp
+ build-mode: manual
+ # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
+ # Use `c-cpp` to analyze code written in C, C++ or both
+ # Use 'java-kotlin' to analyze code written in Java, Kotlin or both
+ # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
+ # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
+ # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
+ # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
+ # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup .NET Core SDK
+ uses: actions/setup-dotnet@v5.0.1
+ with:
+ # TODO: Update this to .NET 10 once PR #3686 is complete.
+ # TODO: Replace this with global-json-file once PR #3797 is complete.
+ dotnet-version: 9.x
+ dotnet-quality: ga
+ #global-json-file: global.json
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v4
+ with:
+ languages: ${{ matrix.language }}
+ build-mode: ${{ matrix.build-mode }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+
+ # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ # queries: security-extended,security-and-quality
+
+ # If the analyze step fails for one of the languages you are analyzing with
+ # "We were unable to automatically build your code", modify the matrix above
+ # to set the build mode to "manual" for that language. Then modify this step
+ # to build your code.
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+ - name: Run manual build steps
+ if: matrix.build-mode == 'manual'
+ shell: bash
+ run: |
+ mkdir packages
+ dotnet build src/
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v4
+ with:
+ category: "/language:${{matrix.language}}"
diff --git a/build.proj b/build.proj
index 90f5f0fc37..fbae7ed6d4 100644
--- a/build.proj
+++ b/build.proj
@@ -58,9 +58,14 @@
+
+
+
+
-
+
+
@@ -221,6 +226,7 @@
-p:TestTargetOS=Windows$(TargetGroup)
--collect "Code coverage"
--results-directory $(ResultsDirectory)
+ --filter "category!=failing%26category!=flaky"
--logger:"trx;LogFilePrefix=Unit-Windows$(TargetGroup)-$(TestSet)"
$(TestCommand.Replace($([System.Environment]::NewLine), " "))
@@ -241,8 +247,9 @@
-p:TestTargetOS=Unixnetcoreapp
--collect "Code coverage"
--results-directory $(ResultsDirectory)
+ --filter "category!=failing%26category!=flaky"
--logger:"trx;LogFilePrefix=Unit-Unixnetcoreapp-$(TestSet)"
-
+
$(TestCommand.Replace($([System.Environment]::NewLine), " "))
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml
index 5e28842000..6b269de720 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml
@@ -3604,7 +3604,9 @@ Before you call , specify t
If you call an `Execute` method after calling , any parameter value that is larger than the value specified by the property is automatically truncated to the original specified size of the parameter, and no truncation errors are returned.
-Output parameters (whether prepared or not) must have a user-specified data type. If you specify a variable length data type, you must also specify the maximum .
+Output parameters (whether prepared or not) must have a user-specified data type. If you specify a variable length data type except vector, you must also specify the maximum .
+
+For vector data types, the property is ignored. The size of the vector is inferred from the of type .
Prior to Visual Studio 2010, threw an exception. Beginning in Visual Studio 2010, this method does not throw an exception.
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml
index 96f72293df..a68c0a323b 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml
@@ -361,7 +361,7 @@ The following example supplies a simple SQL Server connection string in the connection string.
> [!NOTE]
-> Since version 5.x the default value for none Azure endpoints is 1 and for Azure SQL and Azure Synapse has increased to 2 and 5 to imporve the recovery against on high demand Azure endpoints. It should be detected first, and Synapse could be detected as a regular Azure SQL DB endpoint.
+> Since version 5.x the default value for none Azure endpoints is 1 and for Azure SQL and Azure Synapse has increased to 2 and 5 to improve the recovery against on high demand Azure endpoints. It should be detected first, and Synapse could be detected as a regular Azure SQL DB endpoint.
]]>
@@ -539,7 +539,7 @@ This property corresponds to the "Connect Retry Count" key within the
- This property corresponds to the "Data Source", "server", "address", "addr", and "network address" keys within the connection string. Regardless of which of these values has been supplied within the supplied connection string, the connection string created by the SqlConnectionStringBuilder will use the well-known "Data Source" key. The port number can be specified after the server name: server=tcp:servername,portnumber .
+ This property corresponds to the "Data Source", "server", "address", "addr", and "network address" keys within the connection string. Regardless of which of these values has been supplied within the supplied connection string, the connection string created by the SqlConnectionStringBuilder will use the well-known "Data Source" key. The port number can be specified after the server name: server=tcp:servername,port .
When specifying a local instance, always use (local). To force a protocol, add one of the following prefixes: np:(local), tcp:(local), lpc:(local) .
@@ -664,7 +664,7 @@ When `TrustServerCertificate` is false and `Encrypt` is
- Gets or sets a Boolean value that indicates whether the SQL Server connection pooler automatically enlists the connection in the creation thread's current transaction context.
+ Gets or sets a Boolean value that indicates whether the SQL Server connection pool automatically enlists the connection in the creation thread's current transaction context.
The value of the property, or if none has been supplied.
@@ -727,7 +727,7 @@ This property corresponds to the "FailoverPartnerSPN" and "Failover Partner SPN"
[!NOTE]
> This property only applies when using `Encrypt` in or mode, otherwise it is ignored.
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml
index ff54d18a17..1c287a0de4 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml
@@ -967,10 +967,10 @@ The method retur
- Gets the value of the specified column as a .
+ Gets the value of the specified column as a .
- A object representing the column at the given ordinal.
+ A object representing the column at the given ordinal.
The index passed was outside the range of 0 to - 1
@@ -979,7 +979,7 @@ The method retur
An attempt was made to read or access columns in a closed .
- The retrieved data is not compatible with the type.
+ The retrieved data is not compatible with the type.
No conversions are performed; therefore, the data retrieved must already be a vector value, or an exception is generated.
diff --git a/doc/snippets/Microsoft.Data.SqlTypes/SqlVector.xml b/doc/snippets/Microsoft.Data.SqlTypes/SqlVector.xml
index 0157a05787..7dea042145 100644
--- a/doc/snippets/Microsoft.Data.SqlTypes/SqlVector.xml
+++ b/doc/snippets/Microsoft.Data.SqlTypes/SqlVector.xml
@@ -5,20 +5,11 @@
Represents a vector value in SQL Server.
-
-
- Constructs a null vector of the given length. SQL Server requires vector arguments to specify their length even when null.
-
-
- Vector length must be non-negative.
-
-
-
Constructs a vector with the given values.
-
+
@@ -37,13 +28,17 @@
Returns the number of elements in the vector.
-
-
- Returns the number of bytes required to represent this vector when communicating with SQL Server.
-
-
Returns the vector values as a memory region. No copies are made.
+
+
+
+ Constructs a null vector of the given length. SQL Server requires vector arguments to specify their length even when null.
+
+
+ Vector length must be non-negative.
+
+
diff --git a/eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml b/eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml
index 186d8cfdee..068b3a6699 100644
--- a/eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml
+++ b/eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml
@@ -3,160 +3,194 @@
# The .NET Foundation licenses this file to you under the MIT license. #
# See the LICENSE file in the project root for more information. #
#################################################################################
+
+# This job processes code coverage reports generated during test runs,
+# merges them, generates code coverage reports, publishes them to the
+# pipeline, and uploads them to CodeCov.
+
parameters:
+
+ # True to include debug steps.
- name: debug
type: boolean
default: false
- - name: upload
+ # The pool image to use.
+ - name: image
type: string
- default: $(ci_var_uploadTestResult)
- - name: poolName
+ # The agent pool name.
+ - name: pool
type: string
- default: $(defaultHostedPoolName)
- - name: image
- type: string
- default: 'windows-2022'
-
- - name: downloadArtifactsSteps
- type: stepList
- default: []
+ # Array of target frameworks to process code coverage for:
+ #
+ # e.g. [net462, net8.0, net9.0]
+ #
+ - name: targetFrameworks
+ type: object
+
+ # True to upload code coverage results to CodeCov.
+ - name: upload
+ type: boolean
+ default: true
jobs:
-- job: CodeCoverage
- displayName: 'Merge Code Coverage'
-
- variables:
- uploadTestResult: ${{ parameters.upload }}
-
- pool:
- name: '${{ parameters.poolName }}'
- vmImage: ${{ parameters.image }}
-
- steps:
- - ${{if eq(parameters.debug, true)}}:
- - powershell: |
- Get-ChildItem env: | Sort-Object Name
- displayName: 'List Environment Variables [debug]'
-
- - task: NuGetAuthenticate@1
- displayName: 'NuGet Authenticate'
-
- - template: ../steps/ensure-dotnet-version.yml@self
- parameters:
- packageType: 'sdk'
- version: '8.0'
-
- - ${{ parameters.downloadArtifactsSteps }}
-
- - ${{ if eq(parameters.debug, true)}}:
- - powershell: |
- Get-ChildItem $(Build.SourcesDirectory)\coverageNetFx\ -Recurse -File -Filter *.coverage
- displayName: 'List coverageNetFx files [debug]'
-
- - powershell: |
- Get-ChildItem $(Build.SourcesDirectory)\coverageNetCore\ -Recurse -File -Filter *.coverage
- displayName: 'List coverageNetCore files [debug]'
-
- - pwsh: |
- dotnet tool install --global dotnet-coverage
-
- function MergeFiles {
- param(
- [string]$InputDirectoryPath,
- [string]$OutputDirectoryName
- )
-
- $files = Get-ChildItem $InputDirectoryPath -Recurse -File -Filter *.coverage
-
- # echo $files
- mkdir $OutputDirectoryName
- $counter=0
-
- $toProcess = @()
-
- foreach ($file in $files) {
- $toProcess += @{
- File = $file.FullName
- OutputFile = "$OutputDirectoryName\$counter.coveragexml"
+ - job: CodeCoverage
+ displayName: Publish Code Coverage
+
+ pool:
+ name: ${{ parameters.pool }}
+ ${{ if eq(parameters.pool, 'Azure Pipelines') }}:
+ vmImage: ${{ parameters.image }}
+ ${{ else }}:
+ demands:
+ - imageOverride -equals ${{ parameters.image }}
+
+ variables:
+ netFxDir: $(Build.SourcesDirectory)\coverageNetFx
+ netCoreDir: $(Build.SourcesDirectory)\coverageNetCore
+
+ steps:
+ - ${{if eq(parameters.debug, true)}}:
+ - pwsh: |
+ Get-ChildItem env: | Sort-Object Name
+ displayName: 'List Environment Variables [debug]'
+
+ - pwsh: Get-Volume
+ displayName: '[Debug] Show Disk Usage'
+
+ - pwsh: 'Get-ChildItem env: | Sort-Object Name'
+ displayName: '[Debug] List Environment Variables'
+
+ - template: ../steps/ensure-dotnet-version.yml@self
+ parameters:
+ packageType: sdk
+ version: '9.0'
+
+ - pwsh: |
+ dotnet tool install --global dotnet-coverage
+ dotnet tool install --global dotnet-reportgenerator-globaltool
+ displayName: Install dotnet tools
+
+ - ${{ each targetFramework in parameters.targetFrameworks }}:
+ - task: DownloadPipelineArtifact@2
+ displayName: 'Download Coverage Reports [${{ targetFramework }}]'
+ inputs:
+ itemPattern: '**\${{ targetFramework }}*'
+ ${{ if startsWith(targetFramework, 'net4') }}:
+ targetPath: $(netFxDir)
+ ${{ else }}:
+ targetPath: $(netCoreDir)
+
+ - ${{if eq(parameters.debug, true)}}:
+ - pwsh: Get-Volume
+ displayName: '[Debug] Show Disk Usage'
+
+ - pwsh: Get-ChildItem $(netFxDir) -Recurse -File -Filter *.coverage
+ displayName: '[Debug] List coverageNetFx files'
+
+ - pwsh: Get-ChildItem $(netCoreDir) -Recurse -File -Filter *.coverage
+ displayName: '[Debug] List coverageNetCore files'
+
+ - pwsh: |
+ function MergeFiles {
+ param(
+ [string]$InputDirectoryPath,
+ [string]$OutputDirectoryName
+ )
+
+ $files = Get-ChildItem $InputDirectoryPath -Recurse -File -Filter *.coverage
+
+ # echo $files
+ mkdir $OutputDirectoryName
+ $counter=0
+ $toProcess = @()
+
+ foreach ($file in $files) {
+ $toProcess += @{
+ File = $file.FullName
+ OutputFile = "$OutputDirectoryName\$counter.coveragexml"
+ }
+
+ $counter++
}
- $counter++
- }
-
- $jobs = @()
- foreach ($file in $toProcess){
- $jobs += Start-ThreadJob -ScriptBlock {
- $params = $using:file
- & dotnet-coverage merge $($params.File) --output $($params.OutputFile) --output-format xml
- }
- }
-
- Write-Host "Merging started..."
- Wait-Job -Job $jobs
-
- foreach ($job in $jobs) {
- Receive-Job -Job $job -Wait -AutoRemoveJob
- }
- }
-
- MergeFiles -InputDirectoryPath "$(Build.SourcesDirectory)\coverageNetFx\" -OutputDirectoryName "coverageNetFxXml"
- MergeFiles -InputDirectoryPath "$(Build.SourcesDirectory)\coverageNetCore\" -OutputDirectoryName "coverageNetCoreXml"
-
- # dir coverageNetFxXml\
- # dir coverageNetCoreXml\
-
- Write-Host "Clean up disk ... [removing coverageNetFx & coverageNetCore]"
-
- Remove-Item $(Build.SourcesDirectory)\coverageNetFx -Recurse -Force
- Remove-Item $(Build.SourcesDirectory)\coverageNetCore -Recurse -Force
-
- displayName: 'Convert coverage files to xml'
-
- - ${{ if eq(parameters.debug, true)}}:
- - powershell: |
- dir coverageNetFxXml\
- dir coverageNetCoreXml\
- displayName: 'List converted files [debug]'
-
- - pwsh: |
- dotnet tool install dotnet-reportgenerator-globaltool --tool-path tools
-
- $jobs = @()
- $jobs += Start-ThreadJob -ScriptBlock {
- & tools\reportgenerator "-reports:coverageNetFxXml\*.coveragexml" "-targetdir:coveragereportNetFx" "-reporttypes:Cobertura;" "-assemblyfilters:+microsoft.data.sqlclient.dll" "-sourcedirs:$(Build.SourcesDirectory)\src\Microsoft.Data.SqlClient\netfx\src;$(Build.SourcesDirectory)\src\Microsoft.Data.SqlClient\src" "-classfilters:+Microsoft.Data.*"
- }
-
- $jobs += Start-ThreadJob -ScriptBlock {
- & tools\reportgenerator "-reports:coverageNetCoreXml\*.coveragexml" "-targetdir:coveragereportAddOns" "-reporttypes:Cobertura;" "-assemblyfilters:+microsoft.data.sqlclient.alwaysencrypted.azurekeyvaultprovider.dll" "-sourcedirs:$(Build.SourcesDirectory)\src\Microsoft.Data.SqlClient\add-ons\AzureKeyVaultProvider" "-classfilters:+Microsoft.Data.*"
- }
-
- $jobs += Start-ThreadJob -ScriptBlock {
- & tools\reportgenerator "-reports:coverageNetCoreXml\*.coveragexml" "-targetdir:coveragereportNetCore" "-reporttypes:Cobertura;" "-assemblyfilters:+microsoft.data.sqlclient.dll" "-sourcedirs:$(Build.SourcesDirectory)\src\Microsoft.Data.SqlClient\netcore\src;$(Build.SourcesDirectory)\src\Microsoft.Data.SqlClient\src" "-classfilters:+Microsoft.Data.*"
- }
-
- Write-Host "Running ReportGenerator..."
- Wait-Job -Job $jobs
-
- foreach ($job in $jobs) {
- Receive-Job -Job $job -Wait -AutoRemoveJob
- }
- displayName: 'Run ReportGenerator'
-
- - task: PublishCodeCoverageResults@2
- displayName: 'Publish code coverage from netcore'
- inputs:
- summaryFileLocation: '*\Cobertura.xml'
-
- - powershell: |
- #download Codecov CLI
- $ProgressPreference = 'SilentlyContinue'
- Invoke-WebRequest -Uri https://cli.codecov.io/latest/windows/codecov.exe -Outfile codecov.exe
-
- ./codecov --verbose upload-process --fail-on-error -t $(CODECOV_TOKEN) -f "coveragereportNetFx\Cobertura.xml" -F netfx
- ./codecov --verbose upload-process --fail-on-error -t $(CODECOV_TOKEN) -f "coveragereportNetCore\Cobertura.xml" -F netcore
- ./codecov --verbose upload-process --fail-on-error -t $(CODECOV_TOKEN) -f "coveragereportAddOns\Cobertura.xml" -F addons
- displayName: 'Upload to CodeCov'
- condition: and(succeeded(), eq(variables['uploadTestResult'], 'true'))
+ $jobs = @()
+ foreach ($file in $toProcess){
+ $jobs += Start-ThreadJob -ScriptBlock {
+ $params = $using:file
+ & dotnet-coverage merge $($params.File) --output $($params.OutputFile) --output-format xml
+ }
+ }
+
+ Write-Host "Merging started..."
+ Wait-Job -Job $jobs
+
+ foreach ($job in $jobs) {
+ Receive-Job -Job $job -Wait -AutoRemoveJob
+ }
+ }
+
+ MergeFiles -InputDirectoryPath "$(netFxDir)" -OutputDirectoryName "coverageNetFxXml"
+ MergeFiles -InputDirectoryPath "$(netCoreDir)" -OutputDirectoryName "coverageNetCoreXml"
+
+ Write-Host "Removing original coverage files..."
+ Remove-Item $(netFxDir) -Recurse -Force -ErrorAction SilentlyContinue
+ Remove-Item $(netCoreDir) -Recurse -Force -ErrorAction SilentlyContinue
+ displayName: Convert coverage files to xml
+
+ - ${{if eq(parameters.debug, true)}}:
+ - pwsh: Get-Volume
+ displayName: '[Debug] Show Disk Usage'
+
+ - pwsh: |
+ dir coverageNetFxXml\
+ dir coverageNetCoreXml\
+ displayName: '[Debug] List converted files'
+
+ - pwsh: |
+ $jobs = @()
+ $jobs += Start-ThreadJob -ScriptBlock {
+ & reportgenerator "-reports:coverageNetFxXml\*.coveragexml" "-targetdir:coveragereportNetFx" "-reporttypes:Cobertura;" "-assemblyfilters:+microsoft.data.sqlclient.dll" "-sourcedirs:$(Build.SourcesDirectory)\src\Microsoft.Data.SqlClient\netfx\src;$(Build.SourcesDirectory)\src\Microsoft.Data.SqlClient\src" "-classfilters:+Microsoft.Data.*"
+ }
+
+ $jobs += Start-ThreadJob -ScriptBlock {
+ & reportgenerator "-reports:coverageNetCoreXml\*.coveragexml" "-targetdir:coveragereportNetCore" "-reporttypes:Cobertura;" "-assemblyfilters:+microsoft.data.sqlclient.dll" "-sourcedirs:$(Build.SourcesDirectory)\src\Microsoft.Data.SqlClient\netcore\src;$(Build.SourcesDirectory)\src\Microsoft.Data.SqlClient\src" "-classfilters:+Microsoft.Data.*"
+ }
+
+ $jobs += Start-ThreadJob -ScriptBlock {
+ & reportgenerator "-reports:coverageNetCoreXml\*.coveragexml" "-targetdir:coveragereportAddOns" "-reporttypes:Cobertura;" "-assemblyfilters:+microsoft.data.sqlclient.alwaysencrypted.azurekeyvaultprovider.dll" "-sourcedirs:$(Build.SourcesDirectory)\src\Microsoft.Data.SqlClient\add-ons\AzureKeyVaultProvider" "-classfilters:+Microsoft.Data.*"
+ }
+
+ Write-Host "Running ReportGenerator..."
+ Wait-Job -Job $jobs
+
+ foreach ($job in $jobs) {
+ Receive-Job -Job $job -Wait -AutoRemoveJob
+ }
+
+ Write-Host "Removing merged XML files..."
+ Remove-Item coverageNetFxXml -Recurse -Force -ErrorAction SilentlyContinue
+ Remove-Item coverageNetCoreXml -Recurse -Force -ErrorAction SilentlyContinue
+ displayName: Run ReportGenerator
+
+ - ${{if eq(parameters.debug, true)}}:
+ - pwsh: Get-Volume
+ displayName: '[Debug] Show Disk Usage'
+
+ - task: PublishCodeCoverageResults@2
+ displayName: Publish code coverage results
+ inputs:
+ summaryFileLocation: '*\Cobertura.xml'
+
+ - ${{if eq(parameters.upload, true)}}:
+ - pwsh: |
+ #download Codecov CLI
+ $ProgressPreference = 'SilentlyContinue'
+ Invoke-WebRequest -Uri https://cli.codecov.io/latest/windows/codecov.exe -Outfile codecov.exe
+
+ ./codecov --verbose upload-process --fail-on-error -t $(CODECOV_TOKEN) -f "coveragereportNetFx\Cobertura.xml" -F netfx
+ ./codecov --verbose upload-process --fail-on-error -t $(CODECOV_TOKEN) -f "coveragereportNetCore\Cobertura.xml" -F netcore
+ ./codecov --verbose upload-process --fail-on-error -t $(CODECOV_TOKEN) -f "coveragereportAddOns\Cobertura.xml" -F addons
+ displayName: Upload to CodeCov
diff --git a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml
index 9ad2c86e6a..dbf5b10028 100644
--- a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml
+++ b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml
@@ -73,9 +73,18 @@ parameters:
- Project
- Package
+ # The timeout, in minutes, for this job.
+ - name: timeout
+ type: string
+ default: 90
+
jobs:
- job: ${{ format('{0}', coalesce(parameters.jobDisplayName, parameters.image, 'unknown_image')) }}
+ # Some of our tests take longer than the default 60 minutes to run on some
+ # OSes and configurations.
+ timeoutInMinutes: ${{ parameters.timeout }}
+
pool:
name: '${{ parameters.poolName }}'
${{ if eq(parameters.hostedPool, true) }}:
diff --git a/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml b/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml
index e729aaea46..14aea42411 100644
--- a/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml
+++ b/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml
@@ -20,11 +20,21 @@ parameters:
- name: isPreview
type: boolean
+ # The timeout, in minutes, for this job.
+ - name: timeout
+ type: string
+ default: 90
+
jobs:
- job: run_tests_package_reference
displayName: 'Run tests with package reference'
${{ if ne(parameters.dependsOn, 'empty')}}:
dependsOn: '${{parameters.dependsOn }}'
+
+ # Some of our tests take longer than the default 60 minutes to run on some
+ # OSes and configurations.
+ timeoutInMinutes: ${{ parameters.timeout }}
+
pool:
type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs
isCustom: true
diff --git a/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml b/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml
index 3c1671a486..e07685407f 100644
--- a/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml
+++ b/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml
@@ -30,6 +30,11 @@ parameters:
type: jobList
default: []
+ # The timeout, in minutes, for each test job.
+ - name: testsTimeout
+ type: string
+ default: 90
+
stages:
- ${{ each config in parameters.testConfigurations }}:
- ${{ each image in config.value.images }}:
@@ -47,6 +52,7 @@ stages:
parameters:
debug: ${{ parameters.debug }}
buildType: ${{ parameters.buildType }}
+ timeout: ${{ parameters.testsTimeout }}
poolName: ${{ config.value.pool }}
hostedPool: ${{ eq(config.value.hostedPool, true) }}
image: ${{ image.value }}
@@ -72,6 +78,7 @@ stages:
parameters:
debug: ${{ parameters.debug }}
buildType: ${{ parameters.buildType }}
+ timeout: ${{ parameters.testsTimeout }}
poolName: ${{ config.value.pool }}
hostedPool: ${{ eq(config.value.hostedPool, true) }}
image: ${{ image.value }}
diff --git a/eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml b/eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml
index 5cff58cd4e..7568f01608 100644
--- a/eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml
+++ b/eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml
@@ -3,6 +3,11 @@
# The .NET Foundation licenses this file to you under the MIT license. #
# See the LICENSE file in the project root for more information. #
#################################################################################
+
+# This step configures an existing SQL Server running on the local Linux host.
+# For example, our 1ES Hosted Pool has images like ADO-UB20-SQL22 that come with
+# SQL Server 2022 pre-installed and running.
+
parameters:
- name: password
type: string
diff --git a/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml b/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml
index 3e83d6b830..289a113729 100644
--- a/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml
+++ b/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml
@@ -3,6 +3,10 @@
# The .NET Foundation licenses this file to you under the MIT license. #
# See the LICENSE file in the project root for more information. #
#################################################################################
+
+# This step installs the latest SQL Server 2022 onto the macOS host and
+# configures it for use.
+
parameters:
- name: password
type: string
@@ -13,7 +17,7 @@ parameters:
default: and(succeeded(), eq(variables['Agent.OS'], 'Darwin'))
steps:
-# Linux only steps
+# macOS only steps
- bash: |
# The "user" pipeline variable conflicts with homebrew, causing errors during install. Set it back to the pipeline user.
USER=`whoami`
@@ -30,7 +34,7 @@ steps:
docker pull mcr.microsoft.com/mssql/server:2022-latest
# Password for the SA user (required)
- MSSQL_SA_PW=${{parameters.password }}
+ MSSQL_SA_PW=${{ parameters.password }}
docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$MSSQL_SA_PW" -p 1433:1433 -p 1434:1434 --name sql1 --hostname sql1 -d mcr.microsoft.com/mssql/server:2022-latest
@@ -38,29 +42,55 @@ steps:
docker ps -a
- # Connect to server and get the version:
- counter=1
- errstatus=1
- while [ $counter -le 20 ] && [ $errstatus = 1 ]
+ # Connect to the SQL Server container and get its version.
+ #
+ # It can take a while for the docker container to start listening and be
+ # ready for connections, so we will wait for up to 2 minutes, checking every
+ # 3 seconds.
+
+ # Wait 3 seconds between attempts.
+ delay=3
+
+ # Try up to 40 times (2 minutes) to connect.
+ maxAttempts=40
+
+ # Attempt counter.
+ attempt=1
+
+ # Flag to indicate when SQL Server is ready to accept connections.
+ ready=0
+
+ while [ $attempt -le $maxAttempts ]
do
- echo Waiting for SQL Server to start...
- sleep 3
- sqlcmd -S 0.0.0.0 -No -U sa -P $MSSQL_SA_PW -Q "SELECT @@VERSION" 2>$SQLCMD_ERRORS
- errstatus=$?
- ((counter++))
+ echo "Waiting for SQL Server to start (attempt #$attempt of $maxAttempts)..."
+
+ sqlcmd -S 127.0.0.1 -No -U sa -P $MSSQL_SA_PW -Q "SELECT @@VERSION" >> $SQLCMD_ERRORS 2>&1
+
+ # If the command was successful, then the SQL Server is ready.
+ if [ $? -eq 0 ]; then
+ ready=1
+ break
+ fi
+
+ # Increment the attempt counter.
+ ((attempt++))
+
+ # Wait before trying again.
+ sleep $delay
done
- # Display error if connection failed:
- if [ $errstatus = 1 ]
+ # Is the SQL Server ready?
+ if [ $ready -eq 0 ]
then
- echo Cannot connect to SQL Server, installation aborted
+ # No, so report the error(s) and exit.
+ echo Cannot connect to SQL Server; installation aborted; errors were:
cat $SQLCMD_ERRORS
rm -f $SQLCMD_ERRORS
- exit $errstatus
- else
- rm -f $SQLCMD_ERRORS
+ exit 1
fi
+ rm -f $SQLCMD_ERRORS
+
echo "Use sqlcmd to show which IP addresses are being listened on..."
echo 0.0.0.0
sqlcmd -S 0.0.0.0 -No -U sa -P $MSSQL_SA_PW -Q "SELECT @@VERSION" -l 2
@@ -74,7 +104,7 @@ steps:
sqlcmd -No -U sa -P $MSSQL_SA_PW -Q "SELECT @@VERSION" -l 2
echo "Configuring Dedicated Administer Connections to allow remote connections..."
- sqlcmd -S 0.0.0.0 -No -U sa -P $MSSQL_SA_PW -Q "sp_configure 'remote admin connections', 1; RECONFIGURE;"
+ sqlcmd -S 127.0.0.1 -No -U sa -P $MSSQL_SA_PW -Q "sp_configure 'remote admin connections', 1; RECONFIGURE;"
if [ $? = 1 ]
then
echo "Error configuring DAC for remote access."
diff --git a/eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml b/eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml
index d159191b01..6586450e2e 100644
--- a/eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml
+++ b/eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml
@@ -3,6 +3,11 @@
# The .NET Foundation licenses this file to you under the MIT license. #
# See the LICENSE file in the project root for more information. #
#################################################################################
+
+# This step configures an existing SQL Server running on the local Windows host.
+# For example, our 1ES Hosted Pool has images like ADO-MMS22-SQL22 that come
+# with SQL Server 2022 pre-installed and running.
+
parameters:
# Windows only parameters
- name: instanceName
@@ -63,7 +68,7 @@ parameters:
default: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
steps:
-# windows only steps
+# Windows only steps
- powershell: |
try
{
diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml
index 4fa8c6bcbc..09e48415f6 100644
--- a/eng/pipelines/dotnet-sqlclient-ci-core.yml
+++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml
@@ -65,25 +65,40 @@ parameters:
- Project
- Package
+- name: buildConfiguration
+ displayName: 'Build Configuration'
+ default: Release
+ values:
+ - Release
+ - Debug
+
- name: defaultPoolName
type: string
default: $(ci_var_defaultPoolName)
+- name: enableStressTests
+ displayName: Enable Stress Tests
+ type: boolean
+ default: false
+
+# The timeout, in minutes, for each test job.
+- name: testsTimeout
+ type: string
+ default: 90
+
variables:
- template: libraries/ci-build-variables.yml@self
- name: artifactName
value: Artifacts
- - name: defaultHostedPoolName
- value: 'Azure Pipelines'
-
stages:
- stage: build_nugets
displayName: 'Build NuGet Packages'
jobs:
- template: common/templates/jobs/ci-build-nugets-job.yml@self
parameters:
+ configuration: ${{ parameters.buildConfiguration }}
artifactName: $(artifactName)
${{if ne(parameters.SNIVersion, '')}}:
prebuildSteps:
@@ -92,10 +107,21 @@ stages:
SNIVersion: ${{parameters.SNIVersion}}
SNIValidationFeed: ${{parameters.SNIValidationFeed}}
+ - ${{ if eq(parameters.enableStressTests, true) }}:
+ - template: stages/stress-tests-ci-stage.yml@self
+ parameters:
+ buildConfiguration: ${{ parameters.buildConfiguration }}
+ dependsOn: [build_nugets]
+ pipelineArtifactName: $(artifactName)
+ mdsPackageVersion: $(NugetPackageVersion)
+ ${{ if eq(parameters.debug, 'true') }}:
+ verbosity: 'detailed'
+
- template: common/templates/stages/ci-run-tests-stage.yml@self
parameters:
debug: ${{ parameters.debug }}
buildType: ${{ parameters.buildType }}
+ testsTimeout: ${{ parameters.testsTimeout }}
${{ if eq(parameters.buildType, 'Package') }}:
dependsOn: build_nugets
@@ -123,23 +149,15 @@ stages:
- template: common/templates/jobs/ci-code-coverage-job.yml@self
parameters:
debug: ${{ parameters.debug }}
- downloadArtifactsSteps:
- - ${{ each targetFramework in parameters.codeCovTargetFrameworks }}:
- - task: DownloadPipelineArtifact@2
- displayName: 'Download Coverage Reports [${{ targetFramework }}]'
- inputs:
- itemPattern: '**\${{ targetFramework }}*'
- ${{ if contains(targetFramework, 'net4') }}:
- targetPath: '$(Build.SourcesDirectory)\coverageNetFx'
- ${{ else }}:
- targetPath: '$(Build.SourcesDirectory)\coverageNetCore'
+ image: ADO-MMS22-CodeCov
+ pool: ${{ parameters.defaultPoolName }}
+ targetFrameworks: ${{ parameters.codeCovTargetFrameworks }}
# test stages configurations
# self hosted SQL Server on Windows
testConfigurations:
windows_sql_19_x64: # configuration name
pool: ${{parameters.defaultPoolName }} # pool name
- hostedPool: false # whether the pool is hosted or not
images: # list of images to run tests on
Win22_Sql19: ADO-MMS22-SQL19 # stage display name: image name from the pool
TargetFrameworks: ${{parameters.targetFrameworks }} #[net462, net8.0] # list of target frameworks to run
@@ -181,7 +199,6 @@ stages:
windows_sql_19_x86: # configuration name
pool: ${{parameters.defaultPoolName }} # pool name
- hostedPool: false # whether the pool is hosted or not
images: # list of images to run tests on
Win22_Sql19_x86: ADO-MMS22-SQL19 # stage display name: image name from the pool
TargetFrameworks: [net8.0] #[net462, net8.0] # list of target frameworks to run
@@ -483,7 +500,7 @@ stages:
# Self hosted SQL Server on Mac
mac_sql_22:
- pool: $(defaultHostedPoolName)
+ pool: Azure Pipelines
hostedPool: true
images:
MacOSLatest_Sql22: macos-latest
diff --git a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml
index 4956b15c89..953a123487 100644
--- a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml
+++ b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml
@@ -5,36 +5,59 @@
#################################################################################
name: $(DayOfYear)$(Rev:rr)
-trigger:
- batch: true
+
+# Trigger PR validation runs for all pushes to PRs that target the specified
+# branches.
+#
+# https://learn.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#pr-triggers
+#
+pr:
branches:
include:
- - main
- - internal/main
+ - release/6.1
+
paths:
include:
- - src\Microsoft.Data.SqlClient\netcore\ref
- - src\Microsoft.Data.SqlClient\netfx\ref
- - src\Microsoft.Data.SqlClient\ref
+ - .azuredevops
+ - .config
+ - doc
- eng
+ - src
- tools
- - .config
- - Nuget.config
+ - azurepipelines-coverage.yml
+ - build.proj
+ - NuGet.config
-schedules:
-- cron: '0 4 * * Fri'
- displayName: Weekly Thursday 9:00 PM (UTC - 7) Build
+# Commit triggers for CI runs on specified branches.
+#
+# No paths filters are specified, so all commits to the branches will trigger a
+# build.
+#
+trigger:
+ batch: true
branches:
include:
- - internal/main
- always: true
+ - release/6.1
+ - internal/release/6.1
-- cron: '0 0 * * Mon-Fri'
- displayName: Daily build 5:00 PM (UTC - 7) Build
- branches:
- include:
- - main
- always: true
+# Scheduled runs.
+schedules:
+
+ # GitHub on Sundays 04:30 UTC.
+ - cron: '30 4 * * Sun'
+ displayName: Sunday Run
+ branches:
+ include:
+ - release/6.1
+ always: true
+
+ # ADO on Sundays 05:30 UTC.
+ - cron: '30 5 * * Sun'
+ displayName: Sunday Run
+ branches:
+ include:
+ - internal/release/6.1
+ always: true
parameters: # parameters are shown up in ADO UI in a build queue time
- name: 'debug'
@@ -81,6 +104,24 @@ parameters: # parameters are shown up in ADO UI in a build queue time
- Project
- Package
+- name: buildConfiguration
+ displayName: 'Build Configuration'
+ default: Release
+ values:
+ - Release
+ - Debug
+
+- name: enableStressTests
+ displayName: Enable Stress Tests
+ type: boolean
+ default: false
+
+# The timeout, in minutes, for each test job.
+- name: testsTimeout
+ displayName: 'Tests timeout (in minutes)'
+ type: string
+ default: 90
+
extends:
template: dotnet-sqlclient-ci-core.yml@self
parameters:
@@ -92,3 +133,6 @@ extends:
useManagedSNI: ${{ parameters.useManagedSNI }}
codeCovTargetFrameworks: ${{ parameters.codeCovTargetFrameworks }}
buildType: ${{ parameters.buildType }}
+ buildConfiguration: ${{ parameters.buildConfiguration }}
+ enableStressTests: ${{ parameters.enableStressTests }}
+ testsTimeout: ${{ parameters.testsTimeout }}
diff --git a/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml b/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml
index ecdaacfafb..628295dca9 100644
--- a/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml
+++ b/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml
@@ -5,28 +5,59 @@
#################################################################################
name: $(DayOfYear)$(Rev:rr)
-trigger:
- batch: true
+
+# Trigger PR validation runs for all pushes to PRs that target the specified
+# branches.
+#
+# https://learn.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#pr-triggers
+#
+pr:
branches:
include:
- - main
- - internal/main
+ - release/6.1
+
paths:
include:
- - src
+ - .azuredevops
+ - .config
+ - doc
- eng
+ - src
- tools
- - .config
+ - azurepipelines-coverage.yml
- build.proj
- - Nuget.config
+ - NuGet.config
-schedules:
-- cron: '0 5 * * Thu'
- displayName: Weekly Wednesday 10:00 PM (UTC - 7) Build
+# Commit triggers for CI runs on specified branches.
+#
+# No paths filters are specified, so all commits to the branches will trigger a
+# build.
+#
+trigger:
+ batch: true
branches:
include:
- - internal/main
- always: true
+ - release/6.1
+ - internal/release/6.1
+
+# Scheduled runs.
+schedules:
+
+ # GitHub on Sundays 04:00 UTC.
+ - cron: '0 4 * * Sun'
+ displayName: Sunday Run
+ branches:
+ include:
+ - release/6.1
+ always: true
+
+ # ADO on Sundays 05:00 UTC.
+ - cron: '0 5 * * Sun'
+ displayName: Sunday Run
+ branches:
+ include:
+ - internal/release/6.1
+ always: true
parameters: # parameters are shown up in ADO UI in a build queue time
- name: 'debug'
@@ -73,6 +104,24 @@ parameters: # parameters are shown up in ADO UI in a build queue time
- Project
- Package
+- name: buildConfiguration
+ displayName: 'Build Configuration'
+ default: Release
+ values:
+ - Release
+ - Debug
+
+- name: enableStressTests
+ displayName: Enable Stress Tests
+ type: boolean
+ default: false
+
+# The timeout, in minutes, for each test job.
+- name: testsTimeout
+ displayName: 'Tests timeout (in minutes)'
+ type: string
+ default: 90
+
extends:
template: dotnet-sqlclient-ci-core.yml@self
parameters:
@@ -84,3 +133,6 @@ extends:
useManagedSNI: ${{ parameters.useManagedSNI }}
codeCovTargetFrameworks: ${{ parameters.codeCovTargetFrameworks }}
buildType: ${{ parameters.buildType }}
+ buildConfiguration: ${{ parameters.buildConfiguration }}
+ enableStressTests: ${{ parameters.enableStressTests }}
+ testsTimeout: ${{ parameters.testsTimeout }}
diff --git a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml
index c5448be6bc..cabb39e11f 100644
--- a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml
+++ b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml
@@ -8,7 +8,7 @@ name: $(Year:YY)$(DayOfYear)$(Rev:.r)
trigger:
branches:
include:
- - internal/main
+ - internal/release/6.1
paths:
include:
- src
@@ -21,19 +21,13 @@ trigger:
- '*.sh'
schedules:
-- cron: '30 4 * * Mon'
- displayName: Weekly Sunday 9:30 PM (UTC - 7) Build
+- cron: '0 5 * * Mon'
+ displayName: Weekly Sunday 10:00 PM (UTC - 7) Build
branches:
include:
- - internal/main
+ - internal/release/6.1
always: true
-- cron: '30 3 * * Mon-Fri'
- displayName: Mon-Fri 8:30 PM (UTC - 7) Build
- branches:
- include:
- - internal/main
-
parameters: # parameters are shown up in ADO UI in a build queue time
- name: 'debug'
displayName: 'Enable debug output'
@@ -62,6 +56,12 @@ parameters: # parameters are shown up in ADO UI in a build queue time
type: boolean
default: false
+# The timeout, in minutes, for each test job.
+- name: testsTimeout
+ displayName: 'Tests timeout (in minutes)'
+ type: string
+ default: 90
+
variables:
- template: /eng/pipelines/libraries/variables.yml@self
- name: packageFolderName
@@ -161,6 +161,7 @@ extends:
parameters:
packageFolderName: $(packageFolderName)
isPreview: ${{ parameters['isPreview'] }}
+ timeout: ${{ parameters.testsTimeout }}
downloadPackageStep:
download: current
artifact: $(packageFolderName)
diff --git a/eng/pipelines/jobs/build-akv-official-job.yml b/eng/pipelines/jobs/build-akv-official-job.yml
index a4374b773b..af8f546eff 100644
--- a/eng/pipelines/jobs/build-akv-official-job.yml
+++ b/eng/pipelines/jobs/build-akv-official-job.yml
@@ -91,6 +91,7 @@ jobs:
assemblyFileVersion: '${{ parameters.assemblyFileVersion }}'
buildConfiguration: '${{ parameters.buildConfiguration }}'
mdsPackageVersion: '${{ parameters.mdsPackageVersion }}'
+ signingKeyPath: '$(Agent.TempDirectory)/netfxKeypair.snk'
- ${{ each targetFramework in parameters.targetFrameworks }}:
- template: ../steps/compound-extract-akv-apiscan-files-step.yml
@@ -105,6 +106,7 @@ jobs:
parameters:
buildConfiguration: '${{ parameters.buildConfiguration }}'
mdsPackageVersion: '${{ parameters.mdsPackageVersion }}'
+ signingKeyPath: '$(Agent.TempDirectory)/netfxKeypair.snk'
- template: ../steps/compound-esrp-code-signing-step.yml@self
parameters:
diff --git a/eng/pipelines/jobs/stress-tests-ci-job.yml b/eng/pipelines/jobs/stress-tests-ci-job.yml
new file mode 100644
index 0000000000..2e01470fe5
--- /dev/null
+++ b/eng/pipelines/jobs/stress-tests-ci-job.yml
@@ -0,0 +1,214 @@
+################################################################################
+# Licensed to the .NET Foundation under one or more agreements. The .NET
+# Foundation licenses this file to you under the MIT license. See the LICENSE
+# file in the project root for more information.
+################################################################################
+
+# This stage builds and runs stress tests against an MDS NuGet package available
+# as a pipeline artifact.
+#
+# The stress tests are located here:
+#
+# src/Microsoft.Data.SqlClient/tests/StressTests
+#
+# This template defines a job named 'run_stress_tests_job_' that can be
+# depended on by downstream jobs.
+
+parameters:
+ # The suffix to append to the job name.
+ - name: jobNameSuffix
+ type: string
+ default: ''
+
+ # The prefix to prepend to the job's display name:
+ #
+ # [] Run Stress Tests
+ #
+ - name: displayNamePrefix
+ type: string
+ default: ''
+
+ # The name of the Azure Pipelines pool to use.
+ - name: poolName
+ type: string
+ default: ''
+
+ # The pool VM image to use.
+ - name: vmImage
+ type: string
+ default: ''
+
+ # The pipeline step to run to configure SQL Server.
+ #
+ # This step is expected to require no parameters. It must configure a SQL
+ # Server instance listening on localhost for SQL auth via the 'sa' user with
+ # the pipeline variable $(Password) as the password.
+ - name: sqlSetupStep
+ type: string
+ default: ''
+
+ # The name of the pipeline artifact to download that contains the MDS package
+ # to stress test.
+ - name: pipelineArtifactName
+ type: string
+ default: ''
+
+ # The solution file to restore/build.
+ - name: solution
+ type: string
+ default: ''
+
+ # The test project to run.
+ - name: testProject
+ type: string
+ default: ''
+
+ # dotnet CLI arguments for the restore step.
+ - name: restoreArguments
+ type: string
+ default: ''
+
+ # dotnet CLI arguments for the build and run steps.
+ - name: buildArguments
+ type: string
+ default: ''
+
+ # The list of .NET runtimes to test against.
+ - name: netTestRuntimes
+ type: object
+ default: []
+
+ # The list of .NET Framework runtimes to test against.
+ - name: netFrameworkTestRuntimes
+ type: object
+ default: []
+
+ # The stress test config file contents to write to the config file.
+ #
+ # This should point to the SQL Server configured via the sqlSetupStep
+ # parameter, with user 'sa' and password of $(Password).
+ - name: configContent
+ type: string
+ default: ''
+
+jobs:
+- job: run_stress_tests_job_${{ parameters.jobNameSuffix }}
+ displayName: '[${{ parameters.displayNamePrefix }}] Run Stress Tests'
+ pool:
+ name: ${{ parameters.poolName }}
+ ${{ if eq(parameters.poolName, 'Azure Pipelines') }}:
+ vmImage: ${{ parameters.vmImage }}
+ ${{ else }}:
+ demands:
+ - imageOverride -equals ${{ parameters.vmImage }}
+
+ variables:
+ # Stress test command-line arguments.
+ - name: testArguments
+ value: -a SqlClient.Stress.Tests -console
+
+ # Explicitly unset the $PLATFORM environment variable that is set by the
+ # 'ADO Build properties' Library in the ADO SqlClientDrivers public project.
+ # This is defined with a non-standard Platform of 'AnyCPU', and will fail
+ # the builds if left defined. The stress tests solution does not require
+ # any specific Platform, and so its solution file doesn't support any
+ # non-standard platforms.
+ #
+ # Note that Azure Pipelines will inject this variable as PLATFORM into the
+ # environment of all tasks in this job.
+ #
+ # See:
+ # https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch
+ #
+ - name: Platform
+ value: ''
+
+ # Do the same for $CONFIGURATION since we explicitly set it using our
+ # 'buildConfiguration' parameter, and we don't want the environment to
+ # override us.
+ - name: Configuration
+ value: ''
+
+ steps:
+
+ # Install the .NET 9.0 SDK.
+ - task: UseDotNet@2
+ displayName: Install .NET 9.0 SDK
+ inputs:
+ packageType: sdk
+ version: 9.x
+
+ # Install the .NET 8.0 runtime.
+ - task: UseDotNet@2
+ displayName: Install .NET 8.0 Runtime
+ inputs:
+ packageType: runtime
+ version: 8.x
+
+ # Download the pipeline artifact that contains the MDS package to test.
+ - task: DownloadPipelineArtifact@2
+ displayName: Download Pipeline Artifact
+ inputs:
+ artifactName: ${{ parameters.pipelineArtifactName }}
+ # The stress tests solution has a NuGet.config file that configures
+ # sources to look in this packages/ directory.
+ targetPath: $(Build.SourcesDirectory)/packages
+
+ # Setup the local SQL Server.
+ - template: ${{ parameters.sqlSetupStep }}@self
+
+ # We use the 'custom' command because the DotNetCoreCLI@2 task doesn't support
+ # all of our argument combinations for the different build steps.
+
+ # Restore the solution.
+ - task: DotNetCoreCLI@2
+ displayName: Restore Solution
+ inputs:
+ command: custom
+ custom: restore
+ projects: ${{ parameters.solution }}
+ arguments: ${{ parameters.restoreArguments }}
+
+ # Build the solution.
+ - task: DotNetCoreCLI@2
+ displayName: Build Solution
+ inputs:
+ command: custom
+ custom: build
+ projects: ${{ parameters.solution }}
+ arguments: ${{ parameters.buildArguments }} --no-restore
+
+ # Write the config file.
+ - task: PowerShell@2
+ displayName: Write Config File
+ inputs:
+ pwsh: true
+ targetType: inline
+ script: |
+ # Capture the multi-line JSON content into a variable.
+ $content = @"
+ ${{ parameters.configContent }}
+ "@
+
+ # Write the JSON content to the config file.
+ $content | Out-File -FilePath "config.json"
+
+ # Run the stress tests for each .NET runtime.
+ - ${{ each runtime in parameters.netTestRuntimes }}:
+ - task: DotNetCoreCLI@2
+ displayName: Test [${{runtime}}]
+ inputs:
+ command: custom
+ custom: run
+ projects: ${{ parameters.testProject }}
+ arguments: ${{ parameters.buildArguments }} --no-build -f ${{runtime}} -e STRESS_CONFIG_FILE=config.json -- $(testArguments)
+
+ # Run the stress tests for each .NET Framework runtime.
+ - ${{ each runtime in parameters.netFrameworkTestRuntimes }}:
+ - task: DotNetCoreCLI@2
+ displayName: Test [${{runtime}}]
+ inputs:
+ command: custom
+ custom: run
+ projects: ${{ parameters.testProject }}
+ arguments: ${{ parameters.buildArguments }} --no-build -f ${{runtime}} -e STRESS_CONFIG_FILE=config.json -- $(testArguments)
diff --git a/eng/pipelines/libraries/common-variables.yml b/eng/pipelines/libraries/common-variables.yml
index 512175a056..b1b5d5ce7f 100644
--- a/eng/pipelines/libraries/common-variables.yml
+++ b/eng/pipelines/libraries/common-variables.yml
@@ -32,13 +32,13 @@ variables:
- name: Minor
value: '1'
- name: Patch
- value: '0'
+ value: '1'
# Update this for preview releases.
- name: Preview
value: '-preview'
- name: Revision
- value: '2'
+ value: '3'
- name: NugetPackageVersion
value: $(Major).$(Minor).$(Patch)
diff --git a/eng/pipelines/stages/stress-tests-ci-stage.yml b/eng/pipelines/stages/stress-tests-ci-stage.yml
new file mode 100644
index 0000000000..06b41cd421
--- /dev/null
+++ b/eng/pipelines/stages/stress-tests-ci-stage.yml
@@ -0,0 +1,191 @@
+################################################################################
+# Licensed to the .NET Foundation under one or more agreements. The .NET
+# Foundation licenses this file to you under the MIT license. See the LICENSE
+# file in the project root for more information.
+################################################################################
+
+# This stage builds and runs stress tests against an MDS NuGet package available
+# as a pipeline artifact.
+#
+# The stress tests are located here:
+#
+# src/Microsoft.Data.SqlClient/tests/StressTests
+#
+# All tests use a localhost SQL Server configured for SQL auth via the 'sa' user
+# and password of '$(Password)'. The $(Password) variable is defined in the ADO
+# Library "ADO Test Configuration properties", brought in by
+# common/templates/libraries/ci-build-variables.yml.
+#
+# This template defines a stage named 'run_stress_tests_stage' that can be
+# depended on by downstream stages.
+
+parameters:
+ # The type of build to produce (Release or Debug)
+ - name: buildConfiguration
+ displayName: Build Configuration
+ type: string
+ default: Release
+ values:
+ - Release
+ - Debug
+
+ # The names of any stages this stage depends on, for example the stages
+ # that publish the MDS package artifacts we will test.
+ - name: dependsOn
+ displayName: Depends On Stages
+ type: object
+ default: []
+
+ # The name of the pipeline artifact to download that contains the MDS package
+ # to stress test.
+ - name: pipelineArtifactName
+ displayName: Pipeline Artifact Name
+ type: string
+ default: Artifacts
+
+ # The MDS package version to stress test. This version must be available in
+ # one of the configured NuGet sources.
+ - name: mdsPackageVersion
+ displayName: MDS Package Version
+ type: string
+ default: ''
+
+ # The list of .NET runtimes to test against.
+ - name: netTestRuntimes
+ displayName: .NET Test Runtimes
+ type: object
+ default: [net8.0, net9.0]
+
+ # The list of .NET Framework runtimes to test against.
+ - name: netFrameworkTestRuntimes
+ displayName: .NET Framework Test Runtimes
+ type: object
+ default: [net462, net47, net471, net472, net48, net481]
+
+ # The verbosity level for the dotnet CLI commands.
+ - name: verbosity
+ displayName: Dotnet CLI verbosity
+ type: string
+ default: normal
+ values:
+ - quiet
+ - minimal
+ - normal
+ - detailed
+ - diagnostic
+
+stages:
+ - stage: run_stress_tests_stage
+ displayName: Run Stress Tests
+ dependsOn: ${{ parameters.dependsOn }}
+
+ variables:
+ # The directory where dotnet artifacts will be staged. Not to be
+ # confused with pipeline artifact.
+ - name: dotnetArtifactsDir
+ value: $(Build.StagingDirectory)/dotnetArtifacts
+
+ # The solution file to use for all dotnet CLI commands.
+ - name: solution
+ value: src/Microsoft.Data.SqlClient/tests/StressTests/StressTests.slnx
+
+ # The stress test project to run.
+ - name: testProject
+ value: src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj
+
+ # dotnet CLI arguments common to all commands.
+ - name: commonArguments
+ value: >-
+ --verbosity ${{parameters.verbosity}}
+ --artifacts-path $(dotnetArtifactsDir)
+ -p:MdsPackageVersion=${{parameters.mdsPackageVersion}}
+
+ # dotnet CLI arguments for build/run commands.
+ - name: buildArguments
+ value: >-
+ $(commonArguments)
+ --configuration ${{parameters.buildConfiguration}}
+
+ # The contents of the config file to use for all tests. We will write
+ # this to a JSON file for each test job, and then point to it via the
+ # STRESS_CONFIG_FILE environment variable.
+ - name: ConfigContent
+ value: |
+ [
+ {
+ "name": "Azure SQL",
+ "type": "SqlServer",
+ "isDefault": true,
+ "dataSource": "localhost",
+ "user": "sa",
+ "password": "$(Password)",
+ "supportsWindowsAuthentication": false,
+ "isLocal": false,
+ "disableMultiSubnetFailover": true,
+ "disableNamedPipes": true,
+ "encrypt": false
+ }
+ ]
+
+ jobs:
+
+ # --------------------------------------------------------------------------
+ # Build and test on Linux.
+
+ - template: ../jobs/stress-tests-ci-job.yml@self
+ parameters:
+ jobNameSuffix: linux
+ displayNamePrefix: Linux
+ poolName: $(ci_var_defaultPoolName)
+ vmImage: ADO-UB20-SQL22
+ sqlSetupStep: /eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml
+ pipelineArtifactName: ${{ parameters.pipelineArtifactName }}
+ solution: $(solution)
+ testProject: $(testProject)
+ restoreArguments: $(commonArguments)
+ buildArguments: $(buildArguments)
+ netTestRuntimes: ${{ parameters.netTestRuntimes }}
+ configContent: $(ConfigContent)
+
+ # --------------------------------------------------------------------------
+ # Build and test on Windows
+
+ - template: ../jobs/stress-tests-ci-job.yml
+ parameters:
+ jobNameSuffix: windows
+ displayNamePrefix: Win
+ poolName: $(ci_var_defaultPoolName)
+ # The Windows images include a suitable .NET Framework runtime, so we
+ # don't have to install one explicitly.
+ vmImage: ADO-MMS22-SQL22
+ sqlSetupStep: /eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml
+ pipelineArtifactName: ${{ parameters.pipelineArtifactName }}
+ solution: $(solution)
+ testProject: $(testProject)
+ restoreArguments: $(commonArguments)
+ buildArguments: $(buildArguments)
+ netTestRuntimes: ${{ parameters.netTestRuntimes }}
+ # Note that we include the .NET Framework runtimes for test runs on
+ # Windows.
+ netFrameworkTestRuntimes: ${{ parameters.netFrameworkTestRuntimes }}
+ configContent: $(ConfigContent)
+
+ # --------------------------------------------------------------------------
+ # Build and test on macOS.
+
+ - template: ../jobs/stress-tests-ci-job.yml
+ parameters:
+ jobNameSuffix: macos
+ displayNamePrefix: macOS
+ # We don't have any 1ES Hosted Pool images for macOS, so we use a
+ # generic one from Azure Pipelines.
+ poolName: Azure Pipelines
+ vmImage: macos-latest
+ sqlSetupStep: /eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml
+ pipelineArtifactName: ${{ parameters.pipelineArtifactName }}
+ solution: $(solution)
+ testProject: $(testProject)
+ restoreArguments: $(commonArguments)
+ buildArguments: $(buildArguments)
+ netTestRuntimes: ${{ parameters.netTestRuntimes }}
+ configContent: $(ConfigContent)
diff --git a/eng/pipelines/steps/compound-build-akv-step.yml b/eng/pipelines/steps/compound-build-akv-step.yml
index 906dcfaf72..fb6b0e2a06 100644
--- a/eng/pipelines/steps/compound-build-akv-step.yml
+++ b/eng/pipelines/steps/compound-build-akv-step.yml
@@ -19,6 +19,9 @@ parameters:
- name: mdsPackageVersion
type: string
+ - name: signingKeyPath
+ type: string
+
steps:
- task: DownloadSecureFile@1
displayName: 'Download Signing Key'
@@ -48,7 +51,7 @@ steps:
-p:AssemblyFileVersion=${{ parameters.assemblyFileVersion }}
-p:NugetPackageVersion=${{ parameters.mdsPackageVersion }}
-p:ReferenceType=Package
- -p:SigningKeyPath=$(Agent.TempDirectory)/netfxKeypair.snk
+ -p:SigningKeyPath=${{ parameters.signingKeyPath }}
- script: tree /a /f $(BUILD_OUTPUT)
displayName: Output Build Output Tree
diff --git a/eng/pipelines/steps/roslyn-analyzers-akv-step.yml b/eng/pipelines/steps/roslyn-analyzers-akv-step.yml
index 0e05177d5a..d65ec57ca4 100644
--- a/eng/pipelines/steps/roslyn-analyzers-akv-step.yml
+++ b/eng/pipelines/steps/roslyn-analyzers-akv-step.yml
@@ -4,9 +4,13 @@
# See the LICENSE file in the project root for more information. #
#################################################################################
-# @TODO: This can probably be made generic and pass in the command lines for msbuild
-# BUT, they should be kept separate by now as we rebuild build.proj in parallel, we won't
-# affect >1 project at a time.
+# NOTE: Because Roslyn analyzers run with the build process, this step must happen within our
+# build in order to generate logs that Guardian/SDL can consume. HOWEVER - this step will rebuild
+# the project and overwrite any previously build output! Therefore, the command line params in
+# this step and the build step must be the same to avoid packaging invalid binaries!
+# There is a way to avoid using this task and have analyzers run during the main build, but this
+# task will ensure we are using the latest analyzers as per SDL.
+# For more info, please see: https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-mohanb/security-integration/guardian-wiki/sdl-azdo-extension/roslyn-analyzers-build-task
parameters:
- name: buildConfiguration
@@ -15,6 +19,9 @@ parameters:
- name: mdsPackageVersion
type: string
+ - name: signingKeyPath
+ type: string
+
steps:
- task: securedevelopmentteam.vss-secure-development-tools.build-task-roslynanalyzers.RoslynAnalyzers@3
displayName: 'Roslyn Analyzers'
@@ -27,5 +34,6 @@ steps:
-p:Configuration=${{ parameters.buildConfiguration }}
-p:NugetPackageVersion=${{ parameters.mdsPackageVersion }}
-p:ReferenceType=Package
+ -p:SigningKeyPath=${{ parameters.signingKeyPath }}
msBuildVersion: 17.0
setupCommandLinePicker: vs2022
diff --git a/eng/pipelines/variables/akv-official-variables.yml b/eng/pipelines/variables/akv-official-variables.yml
index 4fa517341d..30176ac98b 100644
--- a/eng/pipelines/variables/akv-official-variables.yml
+++ b/eng/pipelines/variables/akv-official-variables.yml
@@ -22,15 +22,15 @@ variables:
# Base Variables -------------------------------------------------------
- name: mdsPackageVersion
- value: '6.0.1'
+ value: '6.1.1'
# @TODO: Version should ideally be pulled from one location (versions.props?)
- name: versionMajor
value: '6'
- name: versionMinor
- value: '0'
+ value: '1'
- name: versionPatch
- value: '0'
+ value: '2'
- name: versionPreview
value: '-preview1'
@@ -38,6 +38,7 @@ variables:
- name: assemblyFileVersion
value: '${{ variables.versionMajor }}.${{ variables.versionMinor }}${{ variables.versionPatch }}.$(Build.BuildNumber)'
- name: nugetPackageVersion
- value: '${{ variables.versionMajor }}.${{ variables.versionMinor }}.${{ variables.versionPatch }}${{ variables.versionPreview }}'
+ value: '${{ variables.versionMajor }}.${{ variables.versionMinor }}.${{ variables.versionPatch }}'
+ #value: '${{ variables.versionMajor }}.${{ variables.versionMinor }}.${{ variables.versionPatch }}${{ variables.versionPreview }}'
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 7d94425123..5cd85e5a1f 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -13,7 +13,7 @@
-
+
@@ -25,7 +25,7 @@
-
+
diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln
index e4d29d999c..c3a9eeb55b 100644
--- a/src/Microsoft.Data.SqlClient.sln
+++ b/src/Microsoft.Data.SqlClient.sln
@@ -287,6 +287,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "variables", "variables", "{
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "jobs", "jobs", "{09352F1D-878F-4F55-8AA2-6E47F1AD37D5}"
+ ProjectSection(SolutionItems) = preProject
+ ..\eng\pipelines\jobs\build-akv-official-job.yml = ..\eng\pipelines\jobs\build-akv-official-job.yml
+ EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "steps", "steps", "{AD738BD4-6A02-4B88-8F93-FBBBA49A74C8}"
ProjectSection(SolutionItems) = preProject
diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AzureSqlKeyCryptographer.cs b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AzureSqlKeyCryptographer.cs
index 0dff6ac786..c4e9eb8396 100644
--- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AzureSqlKeyCryptographer.cs
+++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AzureSqlKeyCryptographer.cs
@@ -7,12 +7,12 @@
using Azure.Security.KeyVault.Keys.Cryptography;
using System;
using System.Collections.Concurrent;
-using System.Threading.Tasks;
+using System.Threading;
using static Azure.Security.KeyVault.Keys.Cryptography.SignatureAlgorithm;
namespace Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider
{
- internal class AzureSqlKeyCryptographer
+ internal sealed class AzureSqlKeyCryptographer : IDisposable
{
///
/// TokenCredential to be used with the KeyClient
@@ -25,16 +25,14 @@ internal class AzureSqlKeyCryptographer
private readonly ConcurrentDictionary _keyClientDictionary = new();
///
- /// Holds references to the fetch key tasks and maps them to their corresponding Azure Key Vault Key Identifier (URI).
- /// These tasks will be used for returning the key in the event that the fetch task has not finished depositing the
- /// key into the key dictionary.
+ /// Holds references to the Azure Key Vault keys and maps them to their corresponding Azure Key Vault Key Identifier (URI).
///
- private readonly ConcurrentDictionary>> _keyFetchTaskDictionary = new();
+ private readonly ConcurrentDictionary _keyDictionary = new();
///
- /// Holds references to the Azure Key Vault keys and maps them to their corresponding Azure Key Vault Key Identifier (URI).
+ /// SemaphoreSlim to ensure thread safety when accessing the key dictionary or making network calls to Azure Key Vault to fetch keys.
///
- private readonly ConcurrentDictionary _keyDictionary = new();
+ private SemaphoreSlim _keyDictionarySemaphore = new(1, 1);
///
/// Holds references to the Azure Key Vault CryptographyClient objects and maps them to their corresponding Azure Key Vault Key Identifier (URI).
@@ -50,20 +48,44 @@ internal AzureSqlKeyCryptographer(TokenCredential tokenCredential)
TokenCredential = tokenCredential;
}
+ ///
+ /// Disposes the SemaphoreSlim used for thread safety.
+ ///
+ public void Dispose()
+ {
+ _keyDictionarySemaphore.Dispose();
+ }
+
///
/// Adds the key, specified by the Key Identifier URI, to the cache.
+ /// Validates the key type and fetches the key from Azure Key Vault if it is not already cached.
///
///
internal void AddKey(string keyIdentifierUri)
{
- if (TheKeyHasNotBeenCached(keyIdentifierUri))
+ // Allow only one thread to proceed to ensure thread safety
+ // as we will need to fetch key information from Azure Key Vault if the key is not found in cache.
+ _keyDictionarySemaphore.Wait();
+
+ try
{
- ParseAKVPath(keyIdentifierUri, out Uri vaultUri, out string keyName, out string keyVersion);
- CreateKeyClient(vaultUri);
- FetchKey(vaultUri, keyName, keyVersion, keyIdentifierUri);
- }
+ if (!_keyDictionary.ContainsKey(keyIdentifierUri))
+ {
+ ParseAKVPath(keyIdentifierUri, out Uri vaultUri, out string keyName, out string keyVersion);
+
+ // Fetch the KeyClient for the Key vault URI.
+ KeyClient keyClient = GetOrCreateKeyClient(vaultUri);
+
+ // Fetch the key from Azure Key Vault.
+ KeyVaultKey key = FetchKeyFromKeyVault(keyClient, keyName, keyVersion);
- bool TheKeyHasNotBeenCached(string k) => !_keyDictionary.ContainsKey(k) && !_keyFetchTaskDictionary.ContainsKey(k);
+ _keyDictionary.AddOrUpdate(keyIdentifierUri, key, (k, v) => key);
+ }
+ }
+ finally
+ {
+ _keyDictionarySemaphore.Release();
+ }
}
///
@@ -75,18 +97,12 @@ internal KeyVaultKey GetKey(string keyIdentifierUri)
{
if (_keyDictionary.TryGetValue(keyIdentifierUri, out KeyVaultKey key))
{
- AKVEventSource.Log.TryTraceEvent("Fetched master key from cache");
+ AKVEventSource.Log.TryTraceEvent("Fetched key name={0} from cache", key.Name);
return key;
}
- if (_keyFetchTaskDictionary.TryGetValue(keyIdentifierUri, out Task> task))
- {
- AKVEventSource.Log.TryTraceEvent("New Master key fetched.");
- return Task.Run(() => task).GetAwaiter().GetResult();
- }
-
// Not a public exception - not likely to occur.
- AKVEventSource.Log.TryTraceEvent("Master key not found.");
+ AKVEventSource.Log.TryTraceEvent("Key not found; URI={0}", keyIdentifierUri);
throw ADP.MasterKeyNotFound(keyIdentifierUri);
}
@@ -95,10 +111,7 @@ internal KeyVaultKey GetKey(string keyIdentifierUri)
///
/// The key vault key identifier URI
///
- internal int GetKeySize(string keyIdentifierUri)
- {
- return GetKey(keyIdentifierUri).Key.N.Length;
- }
+ internal int GetKeySize(string keyIdentifierUri) => GetKey(keyIdentifierUri).Key.N.Length;
///
/// Generates signature based on RSA PKCS#v1.5 scheme using a specified Azure Key Vault Key URL.
@@ -142,41 +155,50 @@ private CryptographyClient GetCryptographyClient(string keyIdentifierUri)
CryptographyClient cryptographyClient = new(GetKey(keyIdentifierUri).Id, TokenCredential);
_cryptoClientDictionary.TryAdd(keyIdentifierUri, cryptographyClient);
-
return cryptographyClient;
}
///
- ///
+ /// Fetches the column encryption key from the Azure Key Vault.
///
- /// The Azure Key Vault URI
+ /// The KeyClient instance
/// The name of the Azure Key Vault key
/// The version of the Azure Key Vault key
- /// The Azure Key Vault key identifier
- private void FetchKey(Uri vaultUri, string keyName, string keyVersion, string keyResourceUri)
+ private KeyVaultKey FetchKeyFromKeyVault(KeyClient keyClient, string keyName, string keyVersion)
{
- Task> fetchKeyTask = FetchKeyFromKeyVault(vaultUri, keyName, keyVersion);
- _keyFetchTaskDictionary.AddOrUpdate(keyResourceUri, fetchKeyTask, (k, v) => fetchKeyTask);
+ AKVEventSource.Log.TryTraceEvent("Fetching key name={0}", keyName);
- fetchKeyTask
- .ContinueWith(k => ValidateRsaKey(k.GetAwaiter().GetResult()))
- .ContinueWith(k => _keyDictionary.AddOrUpdate(keyResourceUri, k.GetAwaiter().GetResult(), (key, v) => k.GetAwaiter().GetResult()));
+ Azure.Response keyResponse = keyClient?.GetKey(keyName, keyVersion);
- Task.Run(() => fetchKeyTask);
+ // Handle the case where the key response is null or contains an error
+ // This can happen if the key does not exist or if there is an issue with the KeyClient.
+ // In such cases, we log the error and throw an exception.
+ if (keyResponse == null || keyResponse.Value == null || keyResponse.GetRawResponse().IsError)
+ {
+ AKVEventSource.Log.TryTraceEvent("Get Key failed to fetch Key from Azure Key Vault for key {0}, version {1}", keyName, keyVersion);
+ if (keyResponse?.GetRawResponse() is Azure.Response response)
+ {
+ AKVEventSource.Log.TryTraceEvent("Response status {0} : {1}", response.Status, response.ReasonPhrase);
+ }
+ throw ADP.GetKeyFailed(keyName);
+ }
+
+ KeyVaultKey key = keyResponse.Value;
+
+ // Validate that the key is of type RSA
+ key = ValidateRsaKey(key);
+ return key;
}
///
- /// Looks up the KeyClient object by it's URI and then fetches the key by name.
+ /// Gets or creates a KeyClient for the specified Azure Key Vault URI.
///
- /// The Azure Key Vault URI
- /// Then name of the key
- /// Then version of the key
+ /// Key Identifier URL
///
- private Task> FetchKeyFromKeyVault(Uri vaultUri, string keyName, string keyVersion)
+ private KeyClient GetOrCreateKeyClient(Uri vaultUri)
{
- _keyClientDictionary.TryGetValue(vaultUri, out KeyClient keyClient);
- AKVEventSource.Log.TryTraceEvent("Fetching requested master key: {0}", keyName);
- return keyClient?.GetKeyAsync(keyName, keyVersion);
+ return _keyClientDictionary.GetOrAdd(
+ vaultUri, (_) => new KeyClient(vaultUri, TokenCredential));
}
///
@@ -184,7 +206,7 @@ private void FetchKey(Uri vaultUri, string keyName, string keyVersion, string ke
///
///
///
- private KeyVaultKey ValidateRsaKey(KeyVaultKey key)
+ private static KeyVaultKey ValidateRsaKey(KeyVaultKey key)
{
if (key.KeyType != KeyType.Rsa && key.KeyType != KeyType.RsaHsm)
{
@@ -195,18 +217,6 @@ private KeyVaultKey ValidateRsaKey(KeyVaultKey key)
return key;
}
- ///
- /// Instantiates and adds a KeyClient to the KeyClient dictionary
- ///
- /// The Azure Key Vault URI
- private void CreateKeyClient(Uri vaultUri)
- {
- if (!_keyClientDictionary.ContainsKey(vaultUri))
- {
- _keyClientDictionary.TryAdd(vaultUri, new KeyClient(vaultUri, TokenCredential));
- }
- }
-
///
/// Validates and parses the Azure Key Vault URI and key name.
///
@@ -214,7 +224,7 @@ private void CreateKeyClient(Uri vaultUri)
/// The Azure Key Vault URI
/// The name of the key
/// The version of the key
- private void ParseAKVPath(string masterKeyPath, out Uri vaultUri, out string masterKeyName, out string masterKeyVersion)
+ private static void ParseAKVPath(string masterKeyPath, out Uri vaultUri, out string masterKeyName, out string masterKeyVersion)
{
Uri masterKeyPathUri = new(masterKeyPath);
vaultUri = new Uri(masterKeyPathUri.GetLeftPart(UriPartial.Authority));
diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/LocalCache.cs b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/LocalCache.cs
index 3e17f5d951..7fbffe0ae6 100644
--- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/LocalCache.cs
+++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/LocalCache.cs
@@ -2,8 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using Microsoft.Extensions.Caching.Memory;
using System;
+using Microsoft.Extensions.Caching.Memory;
using static System.Math;
namespace Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider
@@ -92,6 +92,7 @@ internal TValue GetOrCreate(TKey key, Func createItem)
///
/// Determines whether the LocalCache contains the specified key.
+ /// Used in unit tests to verify that the cache contains the expected entries.
///
///
///
diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj
index 51af5632e3..dcd2e49477 100644
--- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj
+++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj
@@ -18,14 +18,14 @@
true
true
+
-
+
true
$(SigningKeyPath)
-
-
$(SigningKeyPath)
+
$([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(GeneratedSourceFileName)'))
diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/SqlColumnEncryptionAzureKeyVaultProvider.cs b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/SqlColumnEncryptionAzureKeyVaultProvider.cs
index eb8c8d77c4..d4740a1183 100644
--- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/SqlColumnEncryptionAzureKeyVaultProvider.cs
+++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/SqlColumnEncryptionAzureKeyVaultProvider.cs
@@ -4,6 +4,7 @@
using System;
using System.Text;
+using System.Threading;
using Azure.Core;
using Azure.Security.KeyVault.Keys.Cryptography;
using static Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.Validator;
@@ -55,6 +56,8 @@ public class SqlColumnEncryptionAzureKeyVaultProvider : SqlColumnEncryptionKeySt
private readonly static KeyWrapAlgorithm s_keyWrapAlgorithm = KeyWrapAlgorithm.RsaOaep;
+ private SemaphoreSlim _cacheSemaphore = new(1, 1);
+
///
/// List of Trusted Endpoints
///
@@ -69,7 +72,7 @@ public class SqlColumnEncryptionAzureKeyVaultProvider : SqlColumnEncryptionKeySt
///
/// A cache for storing the results of signature verification of column master key metadata.
///
- private readonly LocalCache, bool> _columnMasterKeyMetadataSignatureVerificationCache =
+ private readonly LocalCache, bool> _columnMasterKeyMetadataSignatureVerificationCache =
new(maxSizeLimit: 2000) { TimeToLive = TimeSpan.FromDays(10) };
///
@@ -230,7 +233,7 @@ byte[] DecryptEncryptionKey()
// Get ciphertext
byte[] cipherText = new byte[cipherTextLength];
Array.Copy(encryptedColumnEncryptionKey, currentIndex, cipherText, 0, cipherTextLength);
-
+
currentIndex += cipherTextLength;
// Get signature
@@ -394,17 +397,10 @@ private byte[] CompileMasterKeyMetadata(string masterKeyPath, bool allowEnclaveC
/// An array of bytes to convert.
/// A string of hexadecimal characters
///
- /// Produces a string of hexadecimal character pairs preceded with "0x", where each pair represents the corresponding element in value; for example, "0x7F2C4A00".
+ /// Produces a string of hexadecimal character pairs preceded with "0x", where each pair represents the corresponding element in source; for example, "0x7F2C4A00".
///
private string ToHexString(byte[] source)
- {
- if (source is null)
- {
- return null;
- }
-
- return "0x" + BitConverter.ToString(source).Replace("-", "");
- }
+ => source is null ? null : "0x" + BitConverter.ToString(source).Replace("-", "");
///
/// Returns the cached decrypted column encryption key, or unwraps the encrypted column encryption key if not present.
@@ -415,8 +411,21 @@ private string ToHexString(byte[] source)
///
///
///
- private byte[] GetOrCreateColumnEncryptionKey(string encryptedColumnEncryptionKey, Func createItem)
- => _columnEncryptionKeyCache.GetOrCreate(encryptedColumnEncryptionKey, createItem);
+ private byte[] GetOrCreateColumnEncryptionKey(string encryptedColumnEncryptionKey, Func createItem)
+ {
+ // Allow only one thread to access the cache at a time.
+ _cacheSemaphore.Wait();
+
+ try
+ {
+ return _columnEncryptionKeyCache.GetOrCreate(encryptedColumnEncryptionKey, createItem);
+ }
+ finally
+ {
+ // Release the semaphore to allow other threads to access the cache.
+ _cacheSemaphore.Release();
+ }
+ }
///
/// Returns the cached signature verification result, or proceeds to verify if not present.
diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.Designer.cs
index fc5d88930a..c3b9ce9104 100644
--- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.Designer.cs
@@ -8,6 +8,8 @@
//
//------------------------------------------------------------------------------
+using System.Globalization;
+
namespace Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider
{
///
@@ -88,6 +90,17 @@ internal static string EmptyArgumentInternal
}
}
+ ///
+ /// Looks up a localized string similar to: Failed to fetch key from Azure Key Vault. Key: {0}.
+ ///
+ internal static string GetKeyFailed
+ {
+ get
+ {
+ return ResourceManager.GetString("GetKeyFailed", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Signed hash length does not match the RSA key size..
///
@@ -199,7 +212,18 @@ internal static string InvalidSignatureTemplate
}
///
- /// Looks up a localized string similar to Invalid trusted endpoint specified: '{0}'; a trusted endpoint must have a value..
+ /// Looks up a localized string similar to The key with identifier '{0}' was not found..
+ ///
+ internal static string MasterKeyNotFound
+ {
+ get
+ {
+ return ResourceManager.GetString("MasterKeyNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to One or more of the elements in '{0}' are null or empty or consist of only whitespace..
///
internal static string NullOrWhitespaceForEach
{
diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.resx b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.resx
index 039d1079d5..8775b345ab 100644
--- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.resx
+++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.resx
@@ -118,13 +118,16 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
- One or more of the elements in {0} are null or empty or consist of only whitespace.
+ One or more of the elements in '{0}' are null or empty or consist of only whitespace.
CipherText length does not match the RSA key size.
- Internal error. Empty {0} specified.
+ Internal error. Empty '{0}' specified.
+
+
+ Failed to fetch key from Azure Key Vault. Key: {0}.
The key with identifier '{0}' was not found.
diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Utils.cs b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Utils.cs
index f71080ffab..0eb4dc1c9b 100644
--- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Utils.cs
+++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Utils.cs
@@ -86,7 +86,10 @@ internal static ArgumentException NullOrWhitespaceForEach(string name) =>
new(string.Format(Strings.NullOrWhitespaceForEach, name));
internal static KeyNotFoundException MasterKeyNotFound(string masterKeyPath) =>
- new(string.Format(CultureInfo.InvariantCulture, Strings.InvalidSignatureTemplate, masterKeyPath));
+ new(string.Format(CultureInfo.InvariantCulture, Strings.MasterKeyNotFound, masterKeyPath));
+
+ internal static KeyNotFoundException GetKeyFailed(string masterKeyPath) =>
+ new(string.Format(CultureInfo.InvariantCulture, Strings.GetKeyFailed, masterKeyPath));
internal static FormatException NonRsaKeyFormat(string keyType) =>
new(string.Format(CultureInfo.InvariantCulture, Strings.NonRsaKeyTemplate, keyType));
diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
index 55b68fd9b3..a07133eb07 100644
--- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
@@ -123,23 +123,21 @@ public SqlJson(System.Text.Json.JsonDocument jsonDoc) { }
}
///
- public sealed class SqlVector : System.Data.SqlTypes.INullable
+ public readonly struct SqlVector : System.Data.SqlTypes.INullable
where T : unmanaged
{
///
- public SqlVector(int length) { }
- ///
public SqlVector(System.ReadOnlyMemory memory) { }
///
public bool IsNull => throw null;
///
- public static SqlVector Null => throw null;
+ public static SqlVector? Null => throw null;
///
public int Length { get { throw null; } }
- ///
- public int Size { get { throw null; } }
///
public System.ReadOnlyMemory Memory { get { throw null; } }
+ ///
+ public static SqlVector CreateNull(int length) { throw null; }
}
}
namespace Microsoft.Data.SqlClient
diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj
index 60eff24c1c..0944a4aea0 100644
--- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj
@@ -32,6 +32,7 @@
+
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
index 9dae90ffd0..9152859367 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
@@ -285,6 +285,9 @@
Microsoft\Data\SqlClient\ManagedSni\ConcurrentQueueSemaphore.netcore.cs
+
+ Microsoft\Data\SqlClient\ManagedSni\ResolvedServerSpn.cs
+
Microsoft\Data\SqlClient\ManagedSni\SniAsyncCallback.netcore.cs
@@ -351,9 +354,6 @@
Microsoft\Data\SqlClient\OnChangedEventHandler.cs
-
- Microsoft\Data\SqlClient\Packet.cs
-
Microsoft\Data\SqlClient\ParameterPeekAheadValue.cs
@@ -759,9 +759,6 @@
Microsoft\Data\SqlClient\TdsParserStateObject.cs
-
- Microsoft\Data\SqlClient\TdsParserStateObject.Multiplexer.cs
-
Microsoft\Data\SqlClient\TdsParserStaticMethods.cs
@@ -1034,6 +1031,7 @@
+
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs
index 47349b492e..0497cd76b4 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs
@@ -3623,7 +3623,7 @@ private void CheckNotificationStateAndAutoEnlist()
}
Notification.Options = SqlDependency.GetDefaultComposedOptions(_activeConnection.DataSource,
- InternalTdsConnection.ServerProvidedFailOverPartner,
+ InternalTdsConnection.ServerProvidedFailoverPartner,
identityUserName, _activeConnection.Database);
}
@@ -4180,7 +4180,7 @@ private SqlDataReader TryFetchInputParameterEncryptionInfo(int timeout,
{
// In BatchRPCMode, the actual T-SQL query is in the first parameter and not present as the rpcName, as is the case with non-BatchRPCMode.
// So input parameters start at parameters[1]. parameters[0] is the actual T-SQL Statement. rpcName is sp_executesql.
- if (_RPCList[i].systemParams.Length > 1)
+ if (_RPCList[i].systemParams != null && _RPCList[i].systemParams.Length > 1)
{
_RPCList[i].needsFetchParameterEncryptionMetadata = true;
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs
index 45df50badb..72dec5a28e 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs
@@ -236,7 +236,7 @@ private SqlConnection(SqlConnection connection)
internal static bool TryGetSystemColumnEncryptionKeyStoreProvider(string keyStoreName, out SqlColumnEncryptionKeyStoreProvider provider)
{
- return s_systemColumnEncryptionKeyStoreProviders.TryGetValue(keyStoreName, out provider);
+ return s_systemColumnEncryptionKeyStoreProviders.TryGetValue(keyStoreName, out provider);
}
///
@@ -276,9 +276,9 @@ internal static List GetColumnEncryptionSystemKeyStoreProvidersNames()
{
if (s_systemColumnEncryptionKeyStoreProviders.Count > 0)
{
- return new List(s_systemColumnEncryptionKeyStoreProviders.Keys);
+ return [.. s_systemColumnEncryptionKeyStoreProviders.Keys];
}
- return new List(0);
+ return [];
}
///
@@ -291,13 +291,13 @@ internal List GetColumnEncryptionCustomKeyStoreProvidersNames()
if (_customColumnEncryptionKeyStoreProviders is not null &&
_customColumnEncryptionKeyStoreProviders.Count > 0)
{
- return new List(_customColumnEncryptionKeyStoreProviders.Keys);
+ return [.. _customColumnEncryptionKeyStoreProviders.Keys];
}
if (s_globalCustomColumnEncryptionKeyStoreProviders is not null)
{
- return new List(s_globalCustomColumnEncryptionKeyStoreProviders.Keys);
+ return [.. s_globalCustomColumnEncryptionKeyStoreProviders.Keys];
}
- return new List(0);
+ return [];
}
///
@@ -325,7 +325,9 @@ public static void RegisterColumnEncryptionKeyStoreProviders(IDictionary
+ internal override void ResetConnection()
{
// For implicit pooled connections, if connection reset behavior is specified,
// reset the database and language properties back to default. It is important
@@ -1515,7 +1508,7 @@ private void OpenLoginEnlist(TimeoutTimer timeout,
throw SQL.ROR_FailoverNotSupportedConnString();
}
- if (ServerProvidedFailOverPartner != null)
+ if (ServerProvidedFailoverPartner != null)
{
throw SQL.ROR_FailoverNotSupportedServer(this);
}
@@ -1643,7 +1636,7 @@ private void LoginNoFailover(ServerInfo serverInfo,
newSecurePassword,
attemptOneLoginTimeout);
- if (connectionOptions.MultiSubnetFailover && ServerProvidedFailOverPartner != null)
+ if (connectionOptions.MultiSubnetFailover && ServerProvidedFailoverPartner != null)
{
// connection succeeded: trigger exception if server sends failover partner and MultiSubnetFailover is used
throw SQL.MultiSubnetFailoverWithFailoverPartner(serverProvidedFailoverPartner: true, internalConnection: this);
@@ -1671,7 +1664,7 @@ private void LoginNoFailover(ServerInfo serverInfo,
_currentPacketSize = ConnectionOptions.PacketSize;
_currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage;
CurrentDatabase = _originalDatabase = ConnectionOptions.InitialCatalog;
- _currentFailoverPartner = null;
+ ServerProvidedFailoverPartner = null;
_instanceName = string.Empty;
routingAttempts++;
@@ -1710,7 +1703,7 @@ private void LoginNoFailover(ServerInfo serverInfo,
// We only get here when we failed to connect, but are going to re-try
// Switch to failover logic if the server provided a partner
- if (ServerProvidedFailOverPartner != null)
+ if (ServerProvidedFailoverPartner != null)
{
if (connectionOptions.MultiSubnetFailover)
{
@@ -1726,7 +1719,7 @@ private void LoginNoFailover(ServerInfo serverInfo,
LoginWithFailover(
true, // start by using failover partner, since we already failed to connect to the primary
serverInfo,
- ServerProvidedFailOverPartner,
+ ServerProvidedFailoverPartner,
newPassword,
newSecurePassword,
redirectedUserInstance,
@@ -1748,8 +1741,13 @@ private void LoginNoFailover(ServerInfo serverInfo,
{
// We must wait for CompleteLogin to finish for to have the
// env change from the server to know its designated failover
- // partner; save this information in _currentFailoverPartner.
- PoolGroupProviderInfo.FailoverCheck(false, connectionOptions, ServerProvidedFailOverPartner);
+ // partner; save this information in ServerProvidedFailoverPartner.
+
+ // When ignoring server provided failover partner, we must pass in the original failover partner from the connection string.
+ // Otherwise the pool group's failover partner designation will be updated to point to the server provided value.
+ string actualFailoverPartner = LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner ? string.Empty : ServerProvidedFailoverPartner;
+
+ PoolGroupProviderInfo.FailoverCheck(false, connectionOptions, actualFailoverPartner);
}
CurrentDataSource = originalServerInfo.UserServerName;
}
@@ -1810,7 +1808,7 @@ TimeoutTimer timeout
ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost, connectionOptions.FailoverPartnerSPN);
ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, connectionOptions);
- if (ServerProvidedFailOverPartner == null)
+ if (ServerProvidedFailoverPartner == null)
{
ResolveExtendedServerName(failoverServerInfo, !redirectedUserInstance && failoverHost != primaryServerInfo.UserServerName, connectionOptions);
}
@@ -1869,12 +1867,21 @@ TimeoutTimer timeout
failoverDemandDone = true;
}
- // Primary server may give us a different failover partner than the connection string indicates. Update it
- if (ServerProvidedFailOverPartner != null && failoverServerInfo.ResolvedServerName != ServerProvidedFailOverPartner)
+ // Primary server may give us a different failover partner than the connection string indicates.
+ // Update it only if we are respecting server-provided failover partner values.
+ if (ServerProvidedFailoverPartner != null && failoverServerInfo.ResolvedServerName != ServerProvidedFailoverPartner)
{
- SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, new failover partner={1}", ObjectID, ServerProvidedFailOverPartner);
- failoverServerInfo.SetDerivedNames(string.Empty, ServerProvidedFailOverPartner);
+ if (LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner)
+ {
+ SqlClientEventSource.Log.TryTraceEvent(" {0}, Ignoring server provided failover partner '{1}' due to IgnoreServerProvidedFailoverPartner AppContext switch.", ObjectID, ServerProvidedFailoverPartner);
+ }
+ else
+ {
+ SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, new failover partner={1}", ObjectID, ServerProvidedFailoverPartner);
+ failoverServerInfo.SetDerivedNames(string.Empty, ServerProvidedFailoverPartner);
+ }
}
+
currentServerInfo = failoverServerInfo;
_timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Failover);
}
@@ -1924,7 +1931,7 @@ TimeoutTimer timeout
_currentPacketSize = connectionOptions.PacketSize;
_currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage;
CurrentDatabase = _originalDatabase = connectionOptions.InitialCatalog;
- _currentFailoverPartner = null;
+ ServerProvidedFailoverPartner = null;
_instanceName = string.Empty;
AttemptOneLogin(
@@ -1986,7 +1993,7 @@ TimeoutTimer timeout
_activeDirectoryAuthTimeoutRetryHelper.State = ActiveDirectoryAuthenticationTimeoutRetryState.HasLoggedIn;
// if connected to failover host, but said host doesn't have DbMirroring set up, throw an error
- if (useFailoverHost && ServerProvidedFailOverPartner == null)
+ if (useFailoverHost && ServerProvidedFailoverPartner == null)
{
throw SQL.InvalidPartnerConfiguration(failoverHost, CurrentDatabase);
}
@@ -1995,8 +2002,13 @@ TimeoutTimer timeout
{
// We must wait for CompleteLogin to finish for to have the
// env change from the server to know its designated failover
- // partner; save this information in _currentFailoverPartner.
- PoolGroupProviderInfo.FailoverCheck(useFailoverHost, connectionOptions, ServerProvidedFailOverPartner);
+ // partner.
+
+ // When ignoring server provided failover partner, we must pass in the original failover partner from the connection string.
+ // Otherwise the pool group's failover partner designation will be updated to point to the server provided value.
+ string actualFailoverPartner = LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner ? failoverHost : ServerProvidedFailoverPartner;
+
+ PoolGroupProviderInfo.FailoverCheck(useFailoverHost, connectionOptions, actualFailoverPartner);
}
CurrentDataSource = (useFailoverHost ? failoverHost : primaryServerInfo.UserServerName);
}
@@ -2246,7 +2258,8 @@ internal void OnEnvChange(SqlEnvChange rec)
{
throw SQL.ROR_FailoverNotSupportedServer(this);
}
- _currentFailoverPartner = rec._newValue;
+
+ ServerProvidedFailoverPartner = rec._newValue;
break;
case TdsEnums.ENV_PROMOTETRANSACTION:
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Unix.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Unix.cs
index d71332a25a..3944de7915 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Unix.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Unix.cs
@@ -23,20 +23,18 @@ private void WaitForSSLHandShakeToComplete(ref uint error, ref int protocolVersi
// No - Op
}
- private SNIErrorDetails GetSniErrorDetails()
+ private TdsParserStateObject.SniErrorDetails GetSniErrorDetails()
{
- SNIErrorDetails details;
SniError sniError = SniProxy.Instance.GetLastError();
- details.sniErrorNumber = sniError.sniError;
- details.errorMessage = sniError.errorMessage;
- details.nativeError = sniError.nativeError;
- details.provider = (int)sniError.provider;
- details.lineNumber = sniError.lineNumber;
- details.function = sniError.function;
- details.exception = sniError.exception;
-
- return details;
- }
+ return new(
+ sniError.errorMessage,
+ sniError.nativeError,
+ sniError.sniError,
+ (int)sniError.provider,
+ sniError.lineNumber,
+ sniError.function,
+ sniError.exception);
+ }
}
}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs
index 25bc6f87c5..7576efe32a 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs
@@ -26,10 +26,6 @@ internal void PostReadAsyncForMars()
_pMarsPhysicalConObj.IncrementPendingCallbacks();
SessionHandle handle = _pMarsPhysicalConObj.SessionHandle;
- // we do not need to consider partial packets when making this read because we
- // expect this read to pend. a partial packet should not exist at setup of the
- // parser
- Debug.Assert(_physicalStateObj.PartialPacket==null);
temp = _pMarsPhysicalConObj.ReadAsync(handle, out error);
Debug.Assert(temp.Type == PacketHandle.NativePointerType, "unexpected packet type when requiring NativePointer");
@@ -63,32 +59,31 @@ private void WaitForSSLHandShakeToComplete(ref uint error, ref int protocolVersi
}
}
- private SNIErrorDetails GetSniErrorDetails()
+ private TdsParserStateObject.SniErrorDetails GetSniErrorDetails()
{
- SNIErrorDetails details = new SNIErrorDetails();
-
if (TdsParserStateObjectFactory.UseManagedSNI)
{
SniError sniError = SniProxy.Instance.GetLastError();
- details.sniErrorNumber = sniError.sniError;
- details.errorMessage = sniError.errorMessage;
- details.nativeError = sniError.nativeError;
- details.provider = (int)sniError.provider;
- details.lineNumber = sniError.lineNumber;
- details.function = sniError.function;
- details.exception = sniError.exception;
+ return new(
+ sniError.errorMessage,
+ sniError.nativeError,
+ sniError.sniError,
+ (int)sniError.provider,
+ sniError.lineNumber,
+ sniError.function,
+ sniError.exception);
}
else
{
SniNativeWrapper.SniGetLastError(out Interop.Windows.Sni.SniError sniError);
- details.sniErrorNumber = sniError.sniError;
- details.errorMessage = sniError.errorMessage;
- details.nativeError = sniError.nativeError;
- details.provider = (int)sniError.provider;
- details.lineNumber = sniError.lineNumber;
- details.function = sniError.function;
+ return new(
+ sniError.errorMessage,
+ sniError.nativeError,
+ sniError.sniError,
+ (int)sniError.provider,
+ sniError.lineNumber,
+ sniError.function);
}
- return details;
}
} // tdsparser
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
index e02711fe8d..c020e7bb3e 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -121,8 +121,6 @@ internal sealed partial class TdsParser
private bool _is2022 = false;
- private string[] _serverSpn = null;
-
// SqlStatistics
private SqlStatistics _statistics = null;
@@ -395,7 +393,6 @@ internal void Connect(ServerInfo serverInfo,
}
else
{
- _serverSpn = null;
SqlClientEventSource.Log.TryTraceEvent("TdsParser.Connect | SEC | Connection Object Id {0}, Authentication Mode: {1}", _connHandler.ObjectID,
authType == SqlAuthenticationMethod.NotSpecified ? SqlAuthenticationMethod.SqlPassword.ToString() : authType.ToString());
}
@@ -407,7 +404,6 @@ internal void Connect(ServerInfo serverInfo,
SqlClientEventSource.Log.TryTraceEvent(" Encryption will be disabled as target server is a SQL Local DB instance.");
}
- _serverSpn = null;
_authenticationProvider = null;
// AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server
@@ -446,7 +442,7 @@ internal void Connect(ServerInfo serverInfo,
serverInfo.ExtendedServerName,
timeout,
out instanceName,
- ref _serverSpn,
+ out var resolvedServerSpn,
false,
true,
fParallel,
@@ -459,8 +455,6 @@ internal void Connect(ServerInfo serverInfo,
hostNameInCertificate,
serverCertificateFilename);
- _authenticationProvider?.Initialize(serverInfo, _physicalStateObj, this);
-
if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status)
{
_physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
@@ -546,7 +540,7 @@ internal void Connect(ServerInfo serverInfo,
serverInfo.ExtendedServerName,
timeout,
out instanceName,
- ref _serverSpn,
+ out resolvedServerSpn,
true,
true,
fParallel,
@@ -559,8 +553,6 @@ internal void Connect(ServerInfo serverInfo,
hostNameInCertificate,
serverCertificateFilename);
- _authenticationProvider?.Initialize(serverInfo, _physicalStateObj, this);
-
if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status)
{
_physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
@@ -599,6 +591,11 @@ internal void Connect(ServerInfo serverInfo,
}
SqlClientEventSource.Log.TryTraceEvent(" Prelogin handshake successful");
+ if (_authenticationProvider is { })
+ {
+ _authenticationProvider.Initialize(serverInfo, _physicalStateObj, this, resolvedServerSpn.Primary, resolvedServerSpn.Secondary);
+ }
+
if (_fMARS && marsCapable)
{
// if user explicitly disables mars or mars not supported, don't create the session pool
@@ -744,7 +741,7 @@ private void SendPreLoginHandshake(
// UNDONE - need to do some length verification to ensure packet does not
// get too big!!! Not beyond it's max length!
-
+
for (int option = (int)PreLoginOptions.VERSION; option < (int)PreLoginOptions.NUMOPT; option++)
{
int optionDataSize = 0;
@@ -935,7 +932,7 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(
string serverCertificateFilename)
{
// Assign default values
- marsCapable = _fMARS;
+ marsCapable = _fMARS;
fedAuthRequired = false;
Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
TdsOperationStatus result = _physicalStateObj.TryReadNetworkPacket();
@@ -1450,12 +1447,12 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj)
SqlClientEventSource.Log.TryTraceEvent(" SNIContext must not be None = {0}, _fMARS = {1}, TDS Parser State = {2}", stateObj.DebugOnlyCopyOfSniContext, _fMARS, _state);
#endif
- SNIErrorDetails details = GetSniErrorDetails();
+ TdsParserStateObject.SniErrorDetails details = GetSniErrorDetails();
- if (details.sniErrorNumber != 0)
+ if (details.SniErrorNumber != 0)
{
// handle special SNI error codes that are converted into exception which is not a SqlException.
- switch (details.sniErrorNumber)
+ switch (details.SniErrorNumber)
{
case SniErrors.MultiSubnetFailoverWithMoreThan64IPs:
// Connecting with the MultiSubnetFailover connection option to a SQL Server instance configured with more than 64 IP addresses is not supported.
@@ -1476,8 +1473,8 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj)
}
// PInvoke code automatically sets the length of the string for us
// So no need to look for \0
- string errorMessage = details.errorMessage;
- SqlClientEventSource.Log.TryAdvancedTraceEvent("< sc.TdsParser.ProcessSNIError |ERR|ADV > Error message Detail: {0}", details.errorMessage);
+ string errorMessage = details.ErrorMessage;
+ SqlClientEventSource.Log.TryAdvancedTraceEvent("< sc.TdsParser.ProcessSNIError |ERR|ADV > Error message Detail: {0}", details.ErrorMessage);
/* Format SNI errors and add Context Information
*
@@ -1494,23 +1491,23 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj)
if (TdsParserStateObjectFactory.UseManagedSNI)
{
- Debug.Assert(!string.IsNullOrEmpty(details.errorMessage) || details.sniErrorNumber != 0, "Empty error message received from SNI");
- SqlClientEventSource.Log.TryAdvancedTraceEvent(" Empty error message received from SNI. Error Message = {0}, SNI Error Number ={1}", details.errorMessage, details.sniErrorNumber);
+ Debug.Assert(!string.IsNullOrEmpty(details.ErrorMessage) || details.SniErrorNumber != 0, "Empty error message received from SNI");
+ SqlClientEventSource.Log.TryAdvancedTraceEvent(" Empty error message received from SNI. Error Message = {0}, SNI Error Number ={1}", details.ErrorMessage, details.SniErrorNumber);
}
else
{
- Debug.Assert(!string.IsNullOrEmpty(details.errorMessage), "Empty error message received from SNI");
- SqlClientEventSource.Log.TryAdvancedTraceEvent(" Empty error message received from SNI. Error Message = {0}", details.errorMessage);
+ Debug.Assert(!string.IsNullOrEmpty(details.ErrorMessage), "Empty error message received from SNI");
+ SqlClientEventSource.Log.TryAdvancedTraceEvent(" Empty error message received from SNI. Error Message = {0}", details.ErrorMessage);
}
string sqlContextInfo = StringsHelper.GetResourceString(stateObj.SniContext.ToString());
- string providerRid = string.Format("SNI_PN{0}", details.provider);
+ string providerRid = string.Format("SNI_PN{0}", details.Provider);
string providerName = StringsHelper.GetResourceString(providerRid);
Debug.Assert(!string.IsNullOrEmpty(providerName), $"invalid providerResourceId '{providerRid}'");
- uint win32ErrorCode = details.nativeError;
+ int win32ErrorCode = details.NativeError;
SqlClientEventSource.Log.TryAdvancedTraceEvent(" SNI Native Error Code = {0}", win32ErrorCode);
- if (details.sniErrorNumber == 0)
+ if (details.SniErrorNumber == 0)
{
// Provider error. The message from provider is preceeded with non-localizable info from SNI
// strip provider info from SNI
@@ -1544,7 +1541,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj)
if (TdsParserStateObjectFactory.UseManagedSNI)
{
// SNI error. Append additional error message info if available and hasn't been included.
- string sniLookupMessage = SQL.GetSNIErrorMessage(details.sniErrorNumber);
+ string sniLookupMessage = SQL.GetSNIErrorMessage(details.SniErrorNumber);
errorMessage = (string.IsNullOrEmpty(errorMessage) || errorMessage.Contains(sniLookupMessage))
? sniLookupMessage
: (sniLookupMessage + ": " + errorMessage);
@@ -1552,25 +1549,25 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj)
else
{
// SNI error. Replace the entire message.
- errorMessage = SQL.GetSNIErrorMessage(details.sniErrorNumber);
+ errorMessage = SQL.GetSNIErrorMessage(details.SniErrorNumber);
// If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code
- if (details.sniErrorNumber == SniErrors.LocalDBErrorCode)
+ if (details.SniErrorNumber == SniErrors.LocalDBErrorCode)
{
- errorMessage += LocalDbApi.GetLocalDbMessage((int)details.nativeError);
+ errorMessage += LocalDbApi.GetLocalDbMessage(details.NativeError);
win32ErrorCode = 0;
}
SqlClientEventSource.Log.TryAdvancedTraceEvent(" Extracting the latest exception from native SNI. errorMessage: {0}", errorMessage);
}
}
errorMessage = string.Format("{0} (provider: {1}, error: {2} - {3})",
- sqlContextInfo, providerName, (int)details.sniErrorNumber, errorMessage);
+ sqlContextInfo, providerName, (int)details.SniErrorNumber, errorMessage);
SqlClientEventSource.Log.TryAdvancedTraceErrorEvent(" SNI Error Message. Native Error = {0}, Line Number ={1}, Function ={2}, Exception ={3}, Server = {4}",
- (int)details.nativeError, (int)details.lineNumber, details.function, details.exception, _server);
+ details.NativeError, (int)details.LineNumber, details.Function, details.Exception, _server);
- return new SqlError(infoNumber: (int)details.nativeError, errorState: 0x00, TdsEnums.FATAL_ERROR_CLASS, _server,
- errorMessage, details.function, (int)details.lineNumber, win32ErrorCode: details.nativeError, details.exception);
+ return new SqlError(infoNumber: details.NativeError, errorState: 0x00, TdsEnums.FATAL_ERROR_CLASS, _server,
+ errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode: details.NativeError, details.Exception);
}
}
@@ -2052,19 +2049,11 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle
if (!IsValidTdsToken(token))
{
-#if DEBUG
- string message = stateObj.DumpBuffer();
- Debug.Fail(message);
-#endif
+ Debug.Fail($"unexpected token; token = {token,-2:X2}");
_state = TdsParserState.Broken;
_connHandler.BreakConnection();
SqlClientEventSource.Log.TryTraceEvent(" Potential multi-threaded misuse of connection, unexpected TDS token found {0}", ObjectID);
-#if DEBUG
- throw new InvalidOperationException(message);
-#else
throw SQL.ParsingError();
-#endif
-
}
int tokenLength;
@@ -2181,7 +2170,7 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle
dataStream.BrowseModeInfoConsumed = true;
}
else
- {
+ {
// no dataStream
result = stateObj.TrySkipBytes(tokenLength);
if (result != TdsOperationStatus.Done)
@@ -2195,7 +2184,7 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle
case TdsEnums.SQLDONE:
case TdsEnums.SQLDONEPROC:
case TdsEnums.SQLDONEINPROC:
- {
+ {
// RunBehavior can be modified - see SQL BU DT 269516 & 290090
result = TryProcessDone(cmdHandler, dataStream, ref runBehavior, stateObj);
if (result != TdsOperationStatus.Done)
@@ -4122,7 +4111,6 @@ internal TdsOperationStatus TryProcessReturnValue(int length,
{
return result;
}
-
byte len;
result = stateObj.TryReadByte(out len);
if (result != TdsOperationStatus.Done)
@@ -4330,7 +4318,7 @@ internal TdsOperationStatus TryProcessReturnValue(int length,
{
return result;
}
-
+
if (rec.collation.IsUTF8)
{ // UTF8 collation
rec.encoding = s_utf8EncodingWithoutBom;
@@ -4790,13 +4778,13 @@ internal TdsOperationStatus TryProcessAltMetaData(int cColumns, TdsParserStateOb
{
// internal meta data class
_SqlMetaData col = altMetaDataSet[i];
-
+
result = stateObj.TryReadByte(out _);
if (result != TdsOperationStatus.Done)
{
return result;
}
-
+
result = stateObj.TryReadUInt16(out _);
if (result != TdsOperationStatus.Done)
{
@@ -5287,7 +5275,7 @@ private TdsOperationStatus TryCommonProcessMetaData(TdsParserStateObject stateOb
{
// If the column is encrypted, we should have a valid cipherTable
if (cipherTable != null)
- {
+ {
result = TryProcessTceCryptoMetadata(stateObj, col, cipherTable, columnEncryptionSetting, isReturnValue: false);
if (result != TdsOperationStatus.Done)
{
@@ -5489,7 +5477,7 @@ private TdsOperationStatus TryProcessColInfo(_SqlMetaDataSet columns, SqlDataRea
for (int i = 0; i < columns.Length; i++)
{
_SqlMetaData col = columns[i];
-
+
TdsOperationStatus result = stateObj.TryReadByte(out _);
if (result != TdsOperationStatus.Done)
{
@@ -5573,12 +5561,6 @@ private TdsOperationStatus TryProcessColumnHeaderNoNBC(SqlMetaDataPriv col, TdsP
{
if (col.metaType.IsLong && !col.metaType.IsPlp)
{
- if (stateObj.IsSnapshotContinuing())
- {
- length = (ulong)stateObj.GetSnapshotStorageLength();
- isNull = length == 0;
- return TdsOperationStatus.Done;
- }
//
// we don't care about TextPtrs, simply go after the data after it
//
@@ -6032,17 +6014,34 @@ private TdsOperationStatus TryReadSqlStringValue(SqlBuffer value, byte type, int
if (isPlp)
{
- result = TryReadPlpUnicodeCharsWithContinue(
- stateObj,
- length,
- out string resultString
- );
+ char[] cc = null;
+ bool buffIsRented = false;
+ result = TryReadPlpUnicodeChars(ref cc, 0, length >> 1, stateObj, out length, supportRentedBuff: true, rentedBuff: ref buffIsRented);
if (result == TdsOperationStatus.Done)
{
- s = resultString;
+ if (length > 0)
+ {
+ s = new string(cc, 0, length);
+ }
+ else
+ {
+ s = string.Empty;
+ }
}
- else
+
+ if (buffIsRented)
+ {
+ // do not use clearArray:true on the rented array because it can be massively larger
+ // than the space we've used and we would incur performance clearing memory that
+ // we haven't used and can't leak out information.
+ // clear only the length that we know we have used.
+ cc.AsSpan(0, length).Clear();
+ ArrayPool.Shared.Return(cc, clearArray: false);
+ cc = null;
+ }
+
+ if (result != TdsOperationStatus.Done)
{
return result;
}
@@ -6402,7 +6401,9 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value,
}
else
{
- result = stateObj.TryReadByteArrayWithContinue(length, out b);
+ //Debug.Assert(length > 0 && length < (long)(Int32.MaxValue), "Bad length for column");
+ b = new byte[length];
+ result = stateObj.TryReadByteArray(b, length);
if (result != TdsOperationStatus.Done)
{
return result;
@@ -6473,7 +6474,8 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value,
case TdsEnums.SQLVECTOR:
// Vector data is read as non-plp binary value.
// This is same as reading varbinary(8000).
- result = stateObj.TryReadByteArrayWithContinue(length, out b);
+ byte[] buff = new byte[length];
+ result = stateObj.TryReadByteArray(buff, length);
if (result != TdsOperationStatus.Done)
{
return result;
@@ -6481,13 +6483,13 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value,
// Internally, we use Sqlbinary to deal with varbinary data and store it in
// SqlBuffer as SqlBinary value.
- value.SqlBinary = SqlBinary.WrapBytes(b);
+ value.SqlBinary = SqlBinary.WrapBytes(buff);
// Extract the metadata from the payload and set it as the vector attributes
// in the SqlBuffer. This metadata is further used when constructing a SqlVector
// object from binary payload.
- int elementCount = BinaryPrimitives.ReadUInt16LittleEndian(b.AsSpan(2));
- byte elementType = b[4];
+ int elementCount = BinaryPrimitives.ReadUInt16LittleEndian(buff.AsSpan(2));
+ byte elementType = buff[4];
value.SetVectorInfo(elementCount, elementType, false);
break;
@@ -7394,7 +7396,7 @@ private byte[] SerializeSqlMoney(SqlMoney value, int length, TdsParserStateObjec
private void WriteSqlMoney(SqlMoney value, int length, TdsParserStateObject stateObj)
{
- // UNDONE: can I use SqlMoney.ToInt64()?
+ // UNDONE: can I use SqlMoney.ToInt64()?
int[] bits = decimal.GetBits(value.Value);
// this decimal should be scaled by 10000 (regardless of what the incoming decimal was scaled by)
@@ -8126,22 +8128,14 @@ internal TdsOperationStatus TryGetTokenLength(byte token, TdsParserStateObject s
case TdsEnums.SQLVarCnt:
if (0 != (token & 0x80))
{
- if (stateObj.IsSnapshotContinuing())
- {
- tokenLength = stateObj.GetSnapshotStorageLength();
- Debug.Assert(tokenLength != 0, "stored buffer length on continue must contain the length of the data required for the token");
- }
- else
+ ushort value;
+ result = stateObj.TryReadUInt16(out value);
+ if (result != TdsOperationStatus.Done)
{
- ushort value;
- result = stateObj.TryReadUInt16(out value);
- if (result != TdsOperationStatus.Done)
- {
- tokenLength = 0;
- return result;
- }
- tokenLength = value;
+ tokenLength = 0;
+ return result;
}
+ tokenLength = value;
return TdsOperationStatus.Done;
}
else if (0 == (token & 0x0c))
@@ -9967,7 +9961,7 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet
WriteUDTMetaData(value, names[0], names[1], names[2], stateObj);
- // UNDONE - re-org to use code below to write value!
+ // UNDONE - re-org to use code below to write value!
if (!isNull)
{
WriteUnsignedLong((ulong)udtVal.Length, stateObj); // PLP length
@@ -12441,7 +12435,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int
case TdsEnums.SQLNVARCHAR:
case TdsEnums.SQLNTEXT:
case TdsEnums.SQLXMLTYPE:
- case TdsEnums.SQLJSON:
+ case TdsEnums.SQLJSON:
{
Debug.Assert(!isDataFeed || (value is TextDataFeed || value is XmlDataFeed), "Value must be a TextReader or XmlReader");
Debug.Assert(isDataFeed || (value is string || value is byte[]), "Value is a byte array or string");
@@ -13078,85 +13072,6 @@ internal int ReadPlpUnicodeChars(ref char[] buff, int offst, int len, TdsParserS
return charsRead;
}
- internal TdsOperationStatus TryReadPlpUnicodeCharsWithContinue(TdsParserStateObject stateObj, int length, out string resultString)
- {
- resultString = null;
- char[] temp = null;
- bool buffIsRented = false;
- int startOffset = 0;
- (bool canContinue, bool isStarting, bool isContinuing) = stateObj.GetSnapshotStatuses();
-
- if (canContinue)
- {
- if (isContinuing || isStarting)
- {
- temp = stateObj.TryTakeSnapshotStorage() as char[];
- Debug.Assert(temp == null || length == int.MaxValue || temp.Length == length, "stored buffer length must be null or must have been created with the correct length");
- }
- if (temp != null)
- {
- startOffset = stateObj.GetSnapshotTotalSize();
- }
- }
-
- TdsOperationStatus result = TryReadPlpUnicodeChars(
- ref temp,
- 0,
- length >> 1,
- stateObj,
- out length,
- supportRentedBuff: !canContinue, // do not use the arraypool if we are going to keep the buffer in the snapshot
- rentedBuff: ref buffIsRented,
- startOffset,
- isStarting || isContinuing
- );
-
- if (result == TdsOperationStatus.Done)
- {
- if (length > 0)
- {
- resultString = new string(temp, 0, length);
- }
- else
- {
- resultString = string.Empty;
- }
-
- if (buffIsRented)
- {
- // do not use clearArray:true on the rented array because it can be massively larger
- // than the space we've used and we would incur performance clearing memory that
- // we haven't used and can't leak out information.
- // clear only the length that we know we have used.
- temp.AsSpan(0, length).Clear();
- ArrayPool.Shared.Return(temp, clearArray: false);
- temp = null;
- }
- }
- else if (result == TdsOperationStatus.NeedMoreData)
- {
- if (isStarting || isContinuing)
- {
- stateObj.SetSnapshotStorage(temp);
- }
- }
-
- return result;
- }
-
- internal TdsOperationStatus TryReadPlpUnicodeChars(
- ref char[] buff,
- int offst,
- int len,
- TdsParserStateObject stateObj,
- out int totalCharsRead,
- bool supportRentedBuff,
- ref bool rentedBuff
- )
- {
- return TryReadPlpUnicodeChars(ref buff, offst, len, stateObj, out totalCharsRead, supportRentedBuff, ref rentedBuff, 0, false);
- }
-
// Reads the requested number of chars from a plp data stream, or the entire data if
// requested length is -1 or larger than the actual length of data. First call to this method
// should be preceeded by a call to ReadPlpLength or ReadDataLength.
@@ -13168,13 +13083,11 @@ internal TdsOperationStatus TryReadPlpUnicodeChars(
TdsParserStateObject stateObj,
out int totalCharsRead,
bool supportRentedBuff,
- ref bool rentedBuff,
- int startOffsetByteCount,
- bool writeDataSizeToSnapshot
- )
+ ref bool rentedBuff)
{
int charsRead = 0;
int charsLeft = 0;
+ char[] newbuf;
if (stateObj._longlen == 0)
{
@@ -13184,22 +13097,16 @@ bool writeDataSizeToSnapshot
}
Debug.Assert((ulong)stateObj._longlen != TdsEnums.SQL_PLP_NULL, "Out of sync plp read request");
- Debug.Assert(
- (buff == null && offst == 0)
- ||
- (buff.Length >= offst + len)
- ||
- (buff.Length >= (startOffsetByteCount >> 1) + 1),
- "Invalid length sent to ReadPlpUnicodeChars()!"
- );
+
+ Debug.Assert((buff == null && offst == 0) || (buff.Length >= offst + len), "Invalid length sent to ReadPlpUnicodeChars()!");
charsLeft = len;
// If total data length is known up front from the plp header by being not SQL_PLP_UNKNOWNLEN
// and the number of chars required is less than int.max/2 allocate the entire buffer now to avoid
// later needing to repeatedly allocate new target buffers and copy data as we discover new data
- if (buff == null && stateObj._longlen != TdsEnums.SQL_PLP_UNKNOWNLEN && stateObj._longlen < (int.MaxValue >> 1))
+ if (buff == null && stateObj._longlen != TdsEnums.SQL_PLP_UNKNOWNLEN && len < (int.MaxValue >> 1))
{
- if (supportRentedBuff && stateObj._longlen < 1073741824) // 1 Gib
+ if (supportRentedBuff && len < 1073741824) // 1 Gib
{
buff = ArrayPool.Shared.Rent((int)Math.Min((int)stateObj._longlen, len));
rentedBuff = true;
@@ -13212,11 +13119,6 @@ bool writeDataSizeToSnapshot
}
TdsOperationStatus result;
-
- bool partialReadInProgress = (startOffsetByteCount & 0x1) == 1;
- bool restartingDataSizeCount = startOffsetByteCount == 0;
- int currentPacketId = 0;
-
if (stateObj._longlenleft == 0)
{
result = stateObj.TryReadPlpLength(false, out _);
@@ -13232,103 +13134,63 @@ bool writeDataSizeToSnapshot
}
}
- totalCharsRead = (startOffsetByteCount >> 1);
- charsLeft -= totalCharsRead;
- offst += totalCharsRead;
-
+ totalCharsRead = 0;
while (charsLeft > 0)
{
- if (!partialReadInProgress)
+ charsRead = (int)Math.Min((stateObj._longlenleft + 1) >> 1, (ulong)charsLeft);
+ if ((buff == null) || (buff.Length < (offst + charsRead)))
{
- charsRead = (int)Math.Min((stateObj._longlenleft + 1) >> 1, (ulong)charsLeft);
- if ((buff == null) || (buff.Length < (offst + charsRead)))
+ bool returnRentedBufferAfterCopy = rentedBuff;
+ if (supportRentedBuff && (offst + charsRead) < 1073741824) // 1 Gib
{
- char[] newbuf;
- bool returnRentedBufferAfterCopy = rentedBuff;
- if (supportRentedBuff && (offst + charsRead) < 1073741824) // 1 Gib
- {
- newbuf = ArrayPool.Shared.Rent(offst + charsRead);
- rentedBuff = true;
- }
- else
- {
- // grow by an arbitrary number of packets to avoid needing to reallocate
- // the newbuf on each loop iteration of long packet sequences which causes
- // a performance problem as we spend large amounts of time copying and in gc
- newbuf = new char[offst + charsRead + (stateObj.GetPacketSize() * 8)];
- rentedBuff = false;
- }
-
- if (buff != null)
- {
- Buffer.BlockCopy(buff, 0, newbuf, 0, offst * 2);
- if (returnRentedBufferAfterCopy)
- {
- buff.AsSpan(0, offst).Clear();
- ArrayPool.Shared.Return(buff, clearArray: false);
- }
- }
- buff = newbuf;
- newbuf = null;
+ newbuf = ArrayPool.Shared.Rent(offst + charsRead);
+ rentedBuff = true;
}
- if (charsRead > 0)
+ else
{
- result = TryReadPlpUnicodeCharsChunk(buff, offst, charsRead, stateObj, out charsRead);
- if (result != TdsOperationStatus.Done)
- {
- return result;
- }
- charsLeft -= charsRead;
- offst += charsRead;
- totalCharsRead += charsRead;
+ newbuf = new char[offst + charsRead];
+ rentedBuff = false;
+ }
- if (writeDataSizeToSnapshot)
+ if (buff != null)
+ {
+ Buffer.BlockCopy(buff, 0, newbuf, 0, offst * 2);
+ if (returnRentedBufferAfterCopy)
{
- currentPacketId = IncrementSnapshotDataSize(stateObj, restartingDataSizeCount, currentPacketId, charsRead * 2);
+ buff.AsSpan(0, offst).Clear();
+ ArrayPool.Shared.Return(buff, clearArray: false);
}
}
+ buff = newbuf;
}
-
- // Special case single byte
- if (
- (stateObj._longlenleft == 1 || partialReadInProgress)
- && (charsLeft > 0)
- )
+ if (charsRead > 0)
{
- byte b1 = 0;
- byte b2 = 0;
- if (partialReadInProgress)
+ result = TryReadPlpUnicodeCharsChunk(buff, offst, charsRead, stateObj, out charsRead);
+ if (result != TdsOperationStatus.Done)
{
- partialReadInProgress = false;
- // we're resuming with a partial char in the buffer so we need to load the byte
- // from the char buffer and put it into b1 so we can combine it with the second
- // half later
- b1 = (byte)(buff[offst] & 0x00ff);
+ return result;
}
- else
+ charsLeft -= charsRead;
+ offst += charsRead;
+ totalCharsRead += charsRead;
+ }
+ // Special case single byte left
+ if (stateObj._longlenleft == 1 && (charsLeft > 0))
+ {
+ byte b1;
+ result = stateObj.TryReadByte(out b1);
+ if (result != TdsOperationStatus.Done)
{
- result = stateObj.TryReadByte(out b1);
- if (result != TdsOperationStatus.Done)
- {
- return result;
- }
- stateObj._longlenleft--;
- if (writeDataSizeToSnapshot)
- {
- // we need to write the single b1 byte to the array because we may run out of data
- // and need to wait for another packet
- buff[offst] = (char)((b1 & 0xff));
- currentPacketId = IncrementSnapshotDataSize(stateObj, restartingDataSizeCount, currentPacketId, 1);
- }
-
- result = stateObj.TryReadPlpLength(false, out _);
- if (result != TdsOperationStatus.Done)
- {
- return result;
- }
- Debug.Assert((stateObj._longlenleft != 0), "ReadPlpUnicodeChars: Odd byte left at the end!");
+ return result;
}
-
+ stateObj._longlenleft--;
+ result = stateObj.TryReadPlpLength(false, out _);
+ if (result != TdsOperationStatus.Done)
+ {
+ return result;
+ }
+ Debug.Assert((stateObj._longlenleft != 0), "ReadPlpUnicodeChars: Odd byte left at the end!");
+ byte b2;
result = stateObj.TryReadByte(out b2);
if (result != TdsOperationStatus.Done)
{
@@ -13341,11 +13203,6 @@ bool writeDataSizeToSnapshot
charsRead++;
charsLeft--;
totalCharsRead++;
-
- if (writeDataSizeToSnapshot)
- {
- currentPacketId = IncrementSnapshotDataSize(stateObj, restartingDataSizeCount, currentPacketId, 1);
- }
}
if (stateObj._longlenleft == 0)
{
@@ -13358,41 +13215,9 @@ bool writeDataSizeToSnapshot
}
if (stateObj._longlenleft == 0) // Data read complete
- {
break;
- }
}
return TdsOperationStatus.Done;
-
- static int IncrementSnapshotDataSize(TdsParserStateObject stateObj, bool resetting, int previousPacketId, int value)
- {
- int current = 0;
- if (resetting)
- {
- int currentPacketId = stateObj.GetSnapshotPacketID();
- if (previousPacketId == currentPacketId)
- {
- // we have already reset it the first time we saw it so just add normally
- current = stateObj.GetSnapshotDataSize();
- }
- else
- {
- // a packet we haven't seen before, reset the size
- current = 0;
- }
-
- stateObj.SetSnapshotDataSize(current + value);
-
- // return new packetid so next time we see this packet we know it isn't new
- return currentPacketId;
- }
- else
- {
- current = stateObj.GetSnapshotDataSize();
- stateObj.SetSnapshotDataSize(current + value);
- return previousPacketId;
- }
- }
}
internal int ReadPlpAnsiChars(ref char[] buff, int offst, int len, SqlMetaDataPriv metadata, TdsParserStateObject stateObj)
@@ -13659,15 +13484,14 @@ private TdsOperationStatus TryProcessUDTMetaData(SqlMetaDataPriv metaData, TdsPa
+ " _connHandler = {14}\n\t"
+ " _fMARS = {15}\n\t"
+ " _sessionPool = {16}\n\t"
- + " _sniSpnBuffer = {17}\n\t"
- + " _errors = {18}\n\t"
- + " _warnings = {19}\n\t"
- + " _attentionErrors = {20}\n\t"
- + " _attentionWarnings = {21}\n\t"
- + " _statistics = {22}\n\t"
- + " _statisticsIsInTransaction = {23}\n\t"
- + " _fPreserveTransaction = {24}"
- + " _fParallel = {25}"
+ + " _errors = {17}\n\t"
+ + " _warnings = {18}\n\t"
+ + " _attentionErrors = {19}\n\t"
+ + " _attentionWarnings = {20}\n\t"
+ + " _statistics = {21}\n\t"
+ + " _statisticsIsInTransaction = {22}\n\t"
+ + " _fPreserveTransaction = {23}"
+ + " _fParallel = {24}"
;
internal string TraceString()
{
@@ -13690,7 +13514,6 @@ internal string TraceString()
_connHandler == null ? "(null)" : _connHandler.ObjectID.ToString((IFormatProvider)null),
_fMARS ? bool.TrueString : bool.FalseString,
_sessionPool == null ? "(null)" : _sessionPool.TraceString(),
- _serverSpn == null ? "(null)" : _serverSpn.Length.ToString((IFormatProvider)null),
_physicalStateObj != null ? "(null)" : _physicalStateObj.ErrorCount.ToString((IFormatProvider)null),
_physicalStateObj != null ? "(null)" : _physicalStateObj.WarningCount.ToString((IFormatProvider)null),
_physicalStateObj != null ? "(null)" : _physicalStateObj.PreAttentionErrorCount.ToString((IFormatProvider)null),
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.netcore.cs
index 71ef5e9381..95dd0d9731 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.netcore.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.netcore.cs
@@ -10,17 +10,6 @@ namespace Microsoft.Data.SqlClient
{
internal sealed partial class TdsParser
{
- internal struct SNIErrorDetails
- {
- public string errorMessage;
- public uint nativeError;
- public uint sniErrorNumber;
- public int provider;
- public uint lineNumber;
- public string function;
- public Exception exception;
- }
-
internal static void FillGuidBytes(Guid guid, Span buffer) => guid.TryWriteBytes(buffer);
internal static void FillDoubleBytes(double value, Span buffer) => BinaryPrimitives.TryWriteInt64LittleEndian(buffer, BitConverter.DoubleToInt64Bits(value));
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs
index bb3b07b0e8..4ab44115e0 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs
@@ -11,6 +11,7 @@
using System.Threading.Tasks;
using Microsoft.Data.Common;
using Microsoft.Data.ProviderBase;
+using Microsoft.Data.SqlClient.ManagedSni;
namespace Microsoft.Data.SqlClient
{
@@ -55,7 +56,7 @@ internal TdsParserStateObject(TdsParser parser, TdsParserStateObject physicalCon
AddError(parser.ProcessSNIError(this));
ThrowExceptionAndWarning();
}
-
+
// we post a callback that represents the call to dispose; once the
// object is disposed, the next callback will cause the GC Handle to
// be released.
@@ -71,7 +72,7 @@ internal abstract void CreatePhysicalSNIHandle(
string serverName,
TimeoutTimer timeout,
out byte[] instanceName,
- ref string[] spns,
+ out ResolvedServerSpn resolvedSpn,
bool flushCache,
bool async,
bool fParallel,
@@ -166,22 +167,14 @@ private void ReadSniError(TdsParserStateObject stateObj, uint error)
stateObj.SendAttention(mustTakeWriteLock: true);
PacketHandle syncReadPacket = default;
- bool readFromNetwork = true;
RuntimeHelpers.PrepareConstrainedRegions();
bool shouldDecrement = false;
try
{
Interlocked.Increment(ref _readingCount);
shouldDecrement = true;
- readFromNetwork = !PartialPacketContainsCompletePacket();
- if (readFromNetwork)
- {
- syncReadPacket = ReadSyncOverAsync(stateObj.GetTimeoutRemaining(), out error);
- }
- else
- {
- error = TdsEnums.SNI_SUCCESS;
- }
+
+ syncReadPacket = ReadSyncOverAsync(stateObj.GetTimeoutRemaining(), out error);
Interlocked.Decrement(ref _readingCount);
shouldDecrement = false;
@@ -194,7 +187,7 @@ private void ReadSniError(TdsParserStateObject stateObj, uint error)
}
else
{
- Debug.Assert(!readFromNetwork || !IsValidPacket(syncReadPacket), "unexpected syncReadPacket without corresponding SNIPacketRelease");
+ Debug.Assert(!IsValidPacket(syncReadPacket), "unexpected syncReadPacket without corresponding SNIPacketRelease");
fail = true; // Subsequent read failed, time to give up.
}
}
@@ -205,7 +198,7 @@ private void ReadSniError(TdsParserStateObject stateObj, uint error)
Interlocked.Decrement(ref _readingCount);
}
- if (readFromNetwork && !IsPacketEmpty(syncReadPacket))
+ if (!IsPacketEmpty(syncReadPacket))
{
// Be sure to release packet, otherwise it will be leaked by native.
ReleasePacket(syncReadPacket);
@@ -246,9 +239,60 @@ private void ReadSniError(TdsParserStateObject stateObj, uint error)
AssertValidState();
}
- private uint GetSniPacket(PacketHandle packet, ref uint dataSize)
+ public void ProcessSniPacket(PacketHandle packet, uint error)
{
- return SniPacketGetData(packet, _inBuff, ref dataSize);
+ if (error != 0)
+ {
+ if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken))
+ {
+ // Do nothing with callback if closed or broken and error not 0 - callback can occur
+ // after connection has been closed. PROBLEM IN NETLIB - DESIGN FLAW.
+ return;
+ }
+
+ AddError(_parser.ProcessSNIError(this));
+ AssertValidState();
+ }
+ else
+ {
+ uint dataSize = 0;
+
+ uint getDataError = SniPacketGetData(packet, _inBuff, ref dataSize);
+
+ if (getDataError == TdsEnums.SNI_SUCCESS)
+ {
+ if (_inBuff.Length < dataSize)
+ {
+ Debug.Assert(true, "Unexpected dataSize on Read");
+ throw SQL.InvalidInternalPacketSize(StringsHelper.GetString(Strings.SqlMisc_InvalidArraySizeMessage));
+ }
+
+ _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks;
+ _inBytesRead = (int)dataSize;
+ _inBytesUsed = 0;
+
+ if (_snapshot != null)
+ {
+ _snapshot.AppendPacketData(_inBuff, _inBytesRead);
+ if (_snapshotReplay)
+ {
+ _snapshot.MoveNext();
+#if DEBUG
+ _snapshot.AssertCurrent();
+#endif
+ }
+ }
+
+ SniReadStatisticsAndTracing();
+ SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParser.ReadNetworkPacketAsyncCallback | INFO | ADV | State Object Id {0}, Packet read. In Buffer: {1}, In Bytes Read: {2}", ObjectID, _inBuff, _inBytesRead);
+
+ AssertValidState();
+ }
+ else
+ {
+ throw SQL.ParsingError(ParsingErrorState.ProcessSniPacketFailed);
+ }
+ }
}
private void SetBufferSecureStrings()
@@ -320,7 +364,7 @@ public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error)
bool processFinallyBlock = true;
try
{
- Debug.Assert((packet.Type == 0 && PartialPacketContainsCompletePacket()) || (CheckPacket(packet, source) && source != null), "AsyncResult null on callback");
+ Debug.Assert(CheckPacket(packet, source) && source != null, "AsyncResult null on callback");
if (_parser.MARSOn)
{
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs
index a403f8b556..2845ab68d0 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs
@@ -80,7 +80,7 @@ internal override void CreatePhysicalSNIHandle(
string serverName,
TimeoutTimer timeout,
out byte[] instanceName,
- ref string[] spns,
+ out ResolvedServerSpn resolvedSpn,
bool flushCache,
bool async,
bool parallel,
@@ -93,7 +93,7 @@ internal override void CreatePhysicalSNIHandle(
string hostNameInCertificate,
string serverCertificateFilename)
{
- SniHandle? sessionHandle = SniProxy.CreateConnectionHandle(serverName, timeout, out instanceName, ref spns, serverSPN,
+ SniHandle? sessionHandle = SniProxy.CreateConnectionHandle(serverName, timeout, out instanceName, out resolvedSpn, serverSPN,
flushCache, async, parallel, isIntegratedSecurity, iPAddressPreference, cachedFQDN, ref pendingDNSInfo, tlsFirst,
hostNameInCertificate, serverCertificateFilename);
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs
index 10e9d96eca..14c2609cf3 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs
@@ -155,7 +155,7 @@ internal override void CreatePhysicalSNIHandle(
string serverName,
TimeoutTimer timeout,
out byte[] instanceName,
- ref string[] spns,
+ out Microsoft.Data.SqlClient.ManagedSni.ResolvedServerSpn resolvedSpn,
bool flushCache,
bool async,
bool fParallel,
@@ -189,7 +189,7 @@ internal override void CreatePhysicalSNIHandle(
_sessionHandle = new SNIHandle(myInfo, serverName, ref serverSPN, timeout.MillisecondsRemainingInt, out instanceName,
flushCache, !async, fParallel, ipPreference, cachedDNSInfo, hostNameInCertificate);
- spns = new[] { serverSPN.TrimEnd() };
+ resolvedSpn = new(serverSPN.TrimEnd());
}
protected override uint SniPacketGetData(PacketHandle packet, byte[] _inBuff, ref uint dataSize)
@@ -424,7 +424,7 @@ internal override uint WaitForSSLHandShakeToComplete(out int protocolVersion)
}
else if (nativeProtocol.HasFlag(NativeProtocols.SP_PROT_SSL3_CLIENT) || nativeProtocol.HasFlag(NativeProtocols.SP_PROT_SSL3_SERVER))
{
-// SSL 2.0 and 3.0 are only referenced to log a warning, not explicitly used for connections
+ // SSL 2.0 and 3.0 are only referenced to log a warning, not explicitly used for connections
#pragma warning disable CS0618, CA5397
protocolVersion = (int)SslProtocols.Ssl3;
}
diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
index d7f280ca33..636c39c932 100644
--- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
@@ -2417,22 +2417,20 @@ public SqlJson(System.Text.Json.JsonDocument jsonDoc) { }
}
///
- public sealed class SqlVector : System.Data.SqlTypes.INullable
+ public readonly struct SqlVector : System.Data.SqlTypes.INullable
where T : unmanaged
{
///
- public SqlVector(int length) { }
- ///
public SqlVector(System.ReadOnlyMemory memory) { }
///
public bool IsNull => throw null;
///
- public static SqlVector Null => throw null;
+ public static SqlVector? Null => throw null;
///
public int Length { get { throw null; } }
- ///
- public int Size { get { throw null; } }
///
public System.ReadOnlyMemory Memory { get { throw null; } }
+ ///
+ public static SqlVector CreateNull(int length) { throw null; }
}
}
diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj
index 6b507b5a0a..445fcca7a7 100644
--- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj
@@ -32,6 +32,7 @@
+
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
index 9ce3b09334..172f4c3897 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
@@ -459,9 +459,6 @@
Microsoft\Data\SqlClient\OnChangedEventHandler.cs
-
- Microsoft\Data\SqlClient\Packet.cs
-
Microsoft\Data\SqlClient\PacketHandle.Windows.cs
@@ -855,9 +852,6 @@
Microsoft\Data\SqlClient\TdsParserStateObject.cs
-
- Microsoft\Data\SqlClient\TdsParserStateObject.Multiplexer.cs
-
Microsoft\Data\SqlClient\TdsParserStateObjectFactory.Windows.cs
@@ -961,6 +955,7 @@
+
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs
index 97212843f5..5418fc22f2 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs
@@ -3763,7 +3763,7 @@ private void CheckNotificationStateAndAutoEnlist()
}
Notification.Options = SqlDependency.GetDefaultComposedOptions(_activeConnection.DataSource,
- InternalTdsConnection.ServerProvidedFailOverPartner,
+ InternalTdsConnection.ServerProvidedFailoverPartner,
identityUserName, _activeConnection.Database);
}
@@ -4304,7 +4304,7 @@ private SqlDataReader TryFetchInputParameterEncryptionInfo(int timeout,
{
// In _batchRPCMode, the actual T-SQL query is in the first parameter and not present as the rpcName, as is the case with non-_batchRPCMode.
// So input parameters start at parameters[1]. parameters[0] is the actual T-SQL Statement. rpcName is sp_executesql.
- if (_RPCList[i].systemParams.Length > 1)
+ if (_RPCList[i].systemParams != null && _RPCList[i].systemParams.Length > 1)
{
_RPCList[i].needsFetchParameterEncryptionMetadata = true;
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs
index 46800b4140..f03c58fd15 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs
@@ -318,7 +318,9 @@ public static void RegisterColumnEncryptionKeyStoreProviders(IDictionary
+ internal override void ResetConnection()
{
// For implicit pooled connections, if connection reset behavior is specified,
// reset the database and language properties back to default. It is important
@@ -1521,7 +1514,7 @@ private void OpenLoginEnlist(TimeoutTimer timeout,
throw SQL.ROR_FailoverNotSupportedConnString();
}
- if (ServerProvidedFailOverPartner != null)
+ if (ServerProvidedFailoverPartner != null)
{
throw SQL.ROR_FailoverNotSupportedServer(this);
}
@@ -1671,7 +1664,7 @@ private void LoginNoFailover(ServerInfo serverInfo,
isFirstTransparentAttempt: isFirstTransparentAttempt,
disableTnir: disableTnir);
- if (connectionOptions.MultiSubnetFailover && ServerProvidedFailOverPartner != null)
+ if (connectionOptions.MultiSubnetFailover && ServerProvidedFailoverPartner != null)
{
// connection succeeded: trigger exception if server sends failover partner and MultiSubnetFailover is used
throw SQL.MultiSubnetFailoverWithFailoverPartner(serverProvidedFailoverPartner: true, internalConnection: this);
@@ -1699,7 +1692,7 @@ private void LoginNoFailover(ServerInfo serverInfo,
_currentPacketSize = ConnectionOptions.PacketSize;
_currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage;
CurrentDatabase = _originalDatabase = ConnectionOptions.InitialCatalog;
- _currentFailoverPartner = null;
+ ServerProvidedFailoverPartner = null;
_instanceName = string.Empty;
routingAttempts++;
@@ -1718,8 +1711,11 @@ private void LoginNoFailover(ServerInfo serverInfo,
continue;
}
+ // If state != closed, indicates that the parser encountered an error while processing the
+ // login response (e.g. an explicit error token). Transient network errors that impact
+ // connectivity will result in parser state being closed.
if (_parser == null
- || TdsParserState.Closed != _parser.State
+ || _parser.State != TdsParserState.Closed
|| IsDoNotRetryConnectError(sqlex)
|| timeout.IsExpired)
{
@@ -1738,7 +1734,7 @@ private void LoginNoFailover(ServerInfo serverInfo,
// We only get here when we failed to connect, but are going to re-try
// Switch to failover logic if the server provided a partner
- if (ServerProvidedFailOverPartner != null)
+ if (ServerProvidedFailoverPartner != null)
{
if (connectionOptions.MultiSubnetFailover)
{
@@ -1754,7 +1750,7 @@ private void LoginNoFailover(ServerInfo serverInfo,
LoginWithFailover(
true, // start by using failover partner, since we already failed to connect to the primary
serverInfo,
- ServerProvidedFailOverPartner,
+ ServerProvidedFailoverPartner,
newPassword,
newSecurePassword,
redirectedUserInstance,
@@ -1776,8 +1772,13 @@ private void LoginNoFailover(ServerInfo serverInfo,
{
// We must wait for CompleteLogin to finish for to have the
// env change from the server to know its designated failover
- // partner; save this information in _currentFailoverPartner.
- PoolGroupProviderInfo.FailoverCheck(false, connectionOptions, ServerProvidedFailOverPartner);
+ // partner; save this information in ServerProvidedFailoverPartner.
+
+ // When ignoring server provided failover partner, we must pass in the original failover partner from the connection string.
+ // Otherwise the pool group's failover partner designation will be updated to point to the server provided value.
+ string actualFailoverPartner = LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner ? string.Empty : ServerProvidedFailoverPartner;
+
+ PoolGroupProviderInfo.FailoverCheck(false, connectionOptions, actualFailoverPartner);
}
CurrentDataSource = originalServerInfo.UserServerName;
}
@@ -1861,7 +1862,7 @@ TimeoutTimer timeout
ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost, connectionOptions.FailoverPartnerSPN);
ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, connectionOptions);
- if (ServerProvidedFailOverPartner == null)
+ if (ServerProvidedFailoverPartner == null)
{
ResolveExtendedServerName(failoverServerInfo, !redirectedUserInstance && failoverHost != primaryServerInfo.UserServerName, connectionOptions);
}
@@ -1918,12 +1919,21 @@ TimeoutTimer timeout
failoverDemandDone = true;
}
- // Primary server may give us a different failover partner than the connection string indicates. Update it
- if (ServerProvidedFailOverPartner != null && failoverServerInfo.ResolvedServerName != ServerProvidedFailOverPartner)
+ // Primary server may give us a different failover partner than the connection string indicates.
+ // Update it only if we are respecting server-provided failover partner values.
+ if (ServerProvidedFailoverPartner != null && failoverServerInfo.ResolvedServerName != ServerProvidedFailoverPartner)
{
- SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, new failover partner={1}", ObjectID, ServerProvidedFailOverPartner);
- failoverServerInfo.SetDerivedNames(protocol, ServerProvidedFailOverPartner);
+ if (LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner)
+ {
+ SqlClientEventSource.Log.TryTraceEvent(" {0}, Ignoring server provided failover partner '{1}' due to IgnoreServerProvidedFailoverPartner AppContext switch.", ObjectID, ServerProvidedFailoverPartner);
+ }
+ else
+ {
+ SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, new failover partner={1}", ObjectID, ServerProvidedFailoverPartner);
+ failoverServerInfo.SetDerivedNames(protocol, ServerProvidedFailoverPartner);
+ }
}
+
currentServerInfo = failoverServerInfo;
_timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Failover);
}
@@ -1973,7 +1983,7 @@ TimeoutTimer timeout
_currentPacketSize = connectionOptions.PacketSize;
_currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage;
CurrentDatabase = _originalDatabase = connectionOptions.InitialCatalog;
- _currentFailoverPartner = null;
+ ServerProvidedFailoverPartner = null;
_instanceName = string.Empty;
AttemptOneLogin(
@@ -1999,6 +2009,9 @@ TimeoutTimer timeout
throw; // Caller will call LoginFailure()
}
+ // TODO: It doesn't make sense to connect to an azure sql server instance with a failover partner
+ // specified. Azure SQL Server does not support failover partners. Other availability technologies
+ // like Failover Groups should be used instead.
if (!ADP.IsAzureSqlServerEndpoint(connectionOptions.DataSource) && IsConnectionDoomed)
{
throw;
@@ -2035,7 +2048,7 @@ TimeoutTimer timeout
_activeDirectoryAuthTimeoutRetryHelper.State = ActiveDirectoryAuthenticationTimeoutRetryState.HasLoggedIn;
// if connected to failover host, but said host doesn't have DbMirroring set up, throw an error
- if (useFailoverHost && ServerProvidedFailOverPartner == null)
+ if (useFailoverHost && ServerProvidedFailoverPartner == null)
{
throw SQL.InvalidPartnerConfiguration(failoverHost, CurrentDatabase);
}
@@ -2044,8 +2057,13 @@ TimeoutTimer timeout
{
// We must wait for CompleteLogin to finish for to have the
// env change from the server to know its designated failover
- // partner; save this information in _currentFailoverPartner.
- PoolGroupProviderInfo.FailoverCheck(useFailoverHost, connectionOptions, ServerProvidedFailOverPartner);
+ // partner.
+
+ // When ignoring server provided failover partner, we must pass in the original failover partner from the connection string.
+ // Otherwise the pool group's failover partner designation will be updated to point to the server provided value.
+ string actualFailoverPartner = LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner ? failoverHost : ServerProvidedFailoverPartner;
+
+ PoolGroupProviderInfo.FailoverCheck(useFailoverHost, connectionOptions, actualFailoverPartner);
}
CurrentDataSource = (useFailoverHost ? failoverHost : primaryServerInfo.UserServerName);
}
@@ -2291,7 +2309,7 @@ internal void OnEnvChange(SqlEnvChange rec)
break;
case TdsEnums.ENV_LOGSHIPNODE:
- _currentFailoverPartner = rec._newValue;
+ ServerProvidedFailoverPartner = rec._newValue;
break;
case TdsEnums.ENV_PROMOTETRANSACTION:
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
index 0715bd8205..cce4c8426e 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -121,8 +121,6 @@ internal sealed partial class TdsParser
private bool _is2022 = false;
- private string _serverSpn = null;
-
// SqlStatistics
private SqlStatistics _statistics = null;
@@ -396,6 +394,8 @@ internal void Connect(ServerInfo serverInfo,
Debug.Fail("SNI returned status != success, but no error thrown?");
}
+ string serverSpn = null;
+
//Create LocalDB instance if necessary
if (connHandler.ConnectionOptions.LocalDBInstance != null)
{
@@ -415,13 +415,13 @@ internal void Connect(ServerInfo serverInfo,
if (!string.IsNullOrEmpty(serverInfo.ServerSPN))
{
- _serverSpn = serverInfo.ServerSPN;
+ serverSpn = serverInfo.ServerSPN;
SqlClientEventSource.Log.TryTraceEvent(" Server SPN `{0}` from the connection string is used.", serverInfo.ServerSPN);
}
else
{
// Empty signifies to interop layer that SPN needs to be generated
- _serverSpn = string.Empty;
+ serverSpn = string.Empty;
}
SqlClientEventSource.Log.TryTraceEvent(" SSPI or Active Directory Authentication Library for SQL Server based integrated authentication");
@@ -429,7 +429,6 @@ internal void Connect(ServerInfo serverInfo,
else
{
_authenticationProvider = null;
- _serverSpn = null;
switch (authType)
{
@@ -508,7 +507,7 @@ internal void Connect(ServerInfo serverInfo,
serverInfo.ExtendedServerName,
timeout,
out instanceName,
- ref _serverSpn,
+ ref serverSpn,
false,
true,
fParallel,
@@ -518,8 +517,6 @@ internal void Connect(ServerInfo serverInfo,
FQDNforDNSCache,
hostNameInCertificate);
- _authenticationProvider?.Initialize(serverInfo, _physicalStateObj, this);
-
if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status)
{
_physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
@@ -602,7 +599,7 @@ internal void Connect(ServerInfo serverInfo,
serverInfo.ExtendedServerName,
timeout,
out instanceName,
- ref _serverSpn,
+ ref serverSpn,
true,
true,
fParallel,
@@ -612,8 +609,6 @@ internal void Connect(ServerInfo serverInfo,
serverInfo.ResolvedServerName,
hostNameInCertificate);
- _authenticationProvider?.Initialize(serverInfo, _physicalStateObj, this);
-
if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status)
{
_physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
@@ -648,6 +643,8 @@ internal void Connect(ServerInfo serverInfo,
}
SqlClientEventSource.Log.TryTraceEvent(" Prelogin handshake successful");
+ _authenticationProvider?.Initialize(serverInfo, _physicalStateObj, this, serverSpn);
+
if (_fMARS && marsCapable)
{
// if user explicitly disables mars or mars not supported, don't create the session pool
@@ -1589,7 +1586,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj)
string providerRid = string.Format("SNI_PN{0}", (int)details.provider);
string providerName = StringsHelper.GetString(providerRid);
Debug.Assert(!string.IsNullOrEmpty(providerName), $"invalid providerResourceId '{providerRid}'");
- uint win32ErrorCode = details.nativeError;
+ int win32ErrorCode = details.nativeError;
if (details.sniError == 0)
{
@@ -1627,14 +1624,14 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj)
// If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code
if (details.sniError == SniErrors.LocalDBErrorCode)
{
- errorMessage += LocalDbApi.GetLocalDbMessage((int)details.nativeError);
+ errorMessage += LocalDbApi.GetLocalDbMessage(details.nativeError);
win32ErrorCode = 0;
}
}
errorMessage = string.Format("{0} (provider: {1}, error: {2} - {3})",
sqlContextInfo, providerName, (int)details.sniError, errorMessage);
- return new SqlError((int)details.nativeError, 0x00, TdsEnums.FATAL_ERROR_CLASS,
+ return new SqlError(details.nativeError, 0x00, TdsEnums.FATAL_ERROR_CLASS,
_server, errorMessage, details.function, (int)details.lineNumber, win32ErrorCode);
}
@@ -4174,7 +4171,6 @@ internal TdsOperationStatus TryProcessReturnValue(int length,
{
return result;
}
-
byte len;
result = stateObj.TryReadByte(out len);
if (result != TdsOperationStatus.Done)
@@ -5770,12 +5766,6 @@ private TdsOperationStatus TryProcessColumnHeaderNoNBC(SqlMetaDataPriv col, TdsP
{
if (col.metaType.IsLong && !col.metaType.IsPlp)
{
- if (stateObj.IsSnapshotContinuing())
- {
- length = (ulong)stateObj.GetSnapshotStorageLength();
- isNull = length == 0;
- return TdsOperationStatus.Done;
- }
//
// we don't care about TextPtrs, simply go after the data after it
//
@@ -6229,19 +6219,20 @@ private TdsOperationStatus TryReadSqlStringValue(SqlBuffer value, byte type, int
if (isPlp)
{
- result = TryReadPlpUnicodeCharsWithContinue(
- stateObj,
- length,
- out string resultString
- );
+ char[] cc = null;
+ result = TryReadPlpUnicodeChars(ref cc, 0, length >> 1, stateObj, out length);
- if (result == TdsOperationStatus.Done)
+ if (result != TdsOperationStatus.Done)
{
- s = resultString;
+ return result;
+ }
+ if (length > 0)
+ {
+ s = new string(cc, 0, length);
}
else
{
- return result;
+ s = string.Empty;
}
}
else
@@ -6598,7 +6589,9 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value,
}
else
{
- result = stateObj.TryReadByteArrayWithContinue(length, out b);
+ //Debug.Assert(length > 0 && length < (long)(Int32.MaxValue), "Bad length for column");
+ b = new byte[length];
+ result = stateObj.TryReadByteArray(b, length);
if (result != TdsOperationStatus.Done)
{
return result;
@@ -6669,7 +6662,8 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value,
case TdsEnums.SQLVECTOR:
// Vector data is read as non-plp binary value.
// This is same as reading varbinary(8000).
- result = stateObj.TryReadByteArrayWithContinue(length, out b);
+ byte[] buff = new byte[length];
+ result = stateObj.TryReadByteArray(buff, length);
if (result != TdsOperationStatus.Done)
{
return result;
@@ -6677,13 +6671,13 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value,
// Internally, we use Sqlbinary to deal with varbinary data and store it in
// SqlBuffer as SqlBinary value.
- value.SqlBinary = SqlTypeWorkarounds.SqlBinaryCtor(b, true);
+ value.SqlBinary = SqlTypeWorkarounds.SqlBinaryCtor(buff, true);
// Extract the metadata from the payload and set it as the vector attributes
// in the SqlBuffer. This metadata is further used when constructing a SqlVector
// object from binary payload.
- int elementCount = BinaryPrimitives.ReadUInt16LittleEndian(b.AsSpan(2));
- byte elementType = b[4];
+ int elementCount = BinaryPrimitives.ReadUInt16LittleEndian(buff.AsSpan(2));
+ byte elementType = buff[4];
value.SetVectorInfo(elementCount, elementType, false);
break;
@@ -8308,22 +8302,14 @@ internal TdsOperationStatus TryGetTokenLength(byte token, TdsParserStateObject s
case TdsEnums.SQLVarCnt:
if (0 != (token & 0x80))
{
- if (stateObj.IsSnapshotContinuing())
- {
- tokenLength = stateObj.GetSnapshotStorageLength();
- Debug.Assert(tokenLength != 0, "stored buffer length on continue must contain the length of the data required for the token");
- }
- else
+ ushort value;
+ result = stateObj.TryReadUInt16(out value);
+ if (result != TdsOperationStatus.Done)
{
- ushort value;
- result = stateObj.TryReadUInt16(out value);
- if (result != TdsOperationStatus.Done)
- {
- tokenLength = 0;
- return result;
- }
- tokenLength = value;
+ tokenLength = 0;
+ return result;
}
+ tokenLength = value;
return TdsOperationStatus.Done;
}
else if (0 == (token & 0x0c))
@@ -13249,9 +13235,8 @@ private TdsOperationStatus TryReadPlpUnicodeCharsChunk(char[] buff, int offst, i
internal int ReadPlpUnicodeChars(ref char[] buff, int offst, int len, TdsParserStateObject stateObj)
{
int charsRead;
- bool rentedBuff = false;
Debug.Assert(stateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
- TdsOperationStatus result = TryReadPlpUnicodeChars(ref buff, offst, len, stateObj, out charsRead, supportRentedBuff: false, ref rentedBuff);
+ TdsOperationStatus result = TryReadPlpUnicodeChars(ref buff, offst, len, stateObj, out charsRead);
if (result != TdsOperationStatus.Done)
{
throw SQL.SynchronousCallMayNotPend();
@@ -13259,85 +13244,6 @@ internal int ReadPlpUnicodeChars(ref char[] buff, int offst, int len, TdsParserS
return charsRead;
}
- internal TdsOperationStatus TryReadPlpUnicodeCharsWithContinue(TdsParserStateObject stateObj, int length, out string resultString)
- {
- resultString = null;
- char[] temp = null;
- bool buffIsRented = false;
- int startOffset = 0;
- (bool canContinue, bool isStarting, bool isContinuing) = stateObj.GetSnapshotStatuses();
-
- if (canContinue)
- {
- if (isContinuing || isStarting)
- {
- temp = stateObj.TryTakeSnapshotStorage() as char[];
- Debug.Assert(temp == null || length == int.MaxValue || temp.Length == length, "stored buffer length must be null or must have been created with the correct length");
- }
- if (temp != null)
- {
- startOffset = stateObj.GetSnapshotTotalSize();
- }
- }
-
- TdsOperationStatus result = TryReadPlpUnicodeChars(
- ref temp,
- 0,
- length >> 1,
- stateObj,
- out length,
- supportRentedBuff: !canContinue, // do not use the arraypool if we are going to keep the buffer in the snapshot
- rentedBuff: ref buffIsRented,
- startOffset,
- isStarting || isContinuing
- );
-
- if (result == TdsOperationStatus.Done)
- {
- if (length > 0)
- {
- resultString = new string(temp, 0, length);
- }
- else
- {
- resultString = string.Empty;
- }
-
- if (buffIsRented)
- {
- // do not use clearArray:true on the rented array because it can be massively larger
- // than the space we've used and we would incur performance clearing memory that
- // we haven't used and can't leak out information.
- // clear only the length that we know we have used.
- temp.AsSpan(0, length).Clear();
- ArrayPool.Shared.Return(temp, clearArray: false);
- temp = null;
- }
- }
- else if (result == TdsOperationStatus.NeedMoreData)
- {
- if (isStarting || isContinuing)
- {
- stateObj.SetSnapshotStorage(temp);
- }
- }
-
- return result;
- }
-
- internal TdsOperationStatus TryReadPlpUnicodeChars(
- ref char[] buff,
- int offst,
- int len,
- TdsParserStateObject stateObj,
- out int totalCharsRead,
- bool supportRentedBuff,
- ref bool rentedBuff
- )
- {
- return TryReadPlpUnicodeChars(ref buff, offst, len, stateObj, out totalCharsRead, supportRentedBuff, ref rentedBuff, 0, false);
- }
-
// Reads the requested number of chars from a plp data stream, or the entire data if
// requested length is -1 or larger than the actual length of data. First call to this method
// should be preceeded by a call to ReadPlpLength or ReadDataLength.
@@ -13347,15 +13253,12 @@ internal TdsOperationStatus TryReadPlpUnicodeChars(
int offst,
int len,
TdsParserStateObject stateObj,
- out int totalCharsRead,
- bool supportRentedBuff,
- ref bool rentedBuff,
- int startOffsetByteCount,
- bool writeDataSizeToSnapshot
- )
+ out int totalCharsRead)
{
int charsRead = 0;
int charsLeft = 0;
+ char[] newbuf;
+ TdsOperationStatus result;
if (stateObj._longlen == 0)
{
@@ -13364,40 +13267,18 @@ bool writeDataSizeToSnapshot
return TdsOperationStatus.Done; // No data
}
- Debug.Assert((ulong)stateObj._longlen != TdsEnums.SQL_PLP_NULL, "Out of sync plp read request");
- Debug.Assert(
- (buff == null && offst == 0)
- ||
- (buff.Length >= offst + len)
- ||
- (buff.Length >= (startOffsetByteCount >> 1) + 1),
- "Invalid length sent to ReadPlpUnicodeChars()!"
- );
+ Debug.Assert(((ulong)stateObj._longlen != TdsEnums.SQL_PLP_NULL),
+ "Out of sync plp read request");
+
+ Debug.Assert((buff == null && offst == 0) || (buff.Length >= offst + len), "Invalid length sent to ReadPlpUnicodeChars()!");
charsLeft = len;
- // If total length is known up front, the length isn't specified as unknown
- // and the caller doesn't pass int.max/2 indicating that it doesn't know the length
- // allocate the whole buffer in one shot instead of realloc'ing and copying over each time
- if (buff == null && stateObj._longlen != TdsEnums.SQL_PLP_UNKNOWNLEN && stateObj._longlen < (int.MaxValue >> 1))
+ // If total length is known up front, allocate the whole buffer in one shot instead of realloc'ing and copying over each time
+ if (buff == null && stateObj._longlen != TdsEnums.SQL_PLP_UNKNOWNLEN)
{
- if (supportRentedBuff && stateObj._longlen < 1073741824) // 1 Gib
- {
- buff = ArrayPool.Shared.Rent((int)Math.Min((int)stateObj._longlen, len));
- rentedBuff = true;
- }
- else
- {
- buff = new char[(int)Math.Min((int)stateObj._longlen, len)];
- rentedBuff = false;
- }
+ buff = new char[(int)Math.Min((int)stateObj._longlen, len)];
}
- TdsOperationStatus result;
-
- bool partialReadInProgress = (startOffsetByteCount & 0x1) == 1;
- bool restartingDataSizeCount = startOffsetByteCount == 0;
- int currentPacketId = 0;
-
if (stateObj._longlenleft == 0)
{
result = stateObj.TryReadPlpLength(false, out _);
@@ -13413,104 +13294,48 @@ bool writeDataSizeToSnapshot
}
}
- totalCharsRead = (startOffsetByteCount >> 1);
- charsLeft -= totalCharsRead;
- offst += totalCharsRead;
-
-
+ totalCharsRead = 0;
while (charsLeft > 0)
{
- if (!partialReadInProgress)
+ charsRead = (int)Math.Min((stateObj._longlenleft + 1) >> 1, (ulong)charsLeft);
+ if ((buff == null) || (buff.Length < (offst + charsRead)))
{
- charsRead = (int)Math.Min((stateObj._longlenleft + 1) >> 1, (ulong)charsLeft);
- if ((buff == null) || (buff.Length < (offst + charsRead)))
+ // Grow the array
+ newbuf = new char[offst + charsRead];
+ if (buff != null)
{
- char[] newbuf;
- bool returnRentedBufferAfterCopy = rentedBuff;
- if (supportRentedBuff && (offst + charsRead) < 1073741824) // 1 Gib
- {
- newbuf = ArrayPool.Shared.Rent(offst + charsRead);
- rentedBuff = true;
- }
- else
- {
- // grow by an arbitrary number of packets to avoid needing to reallocate
- // the newbuf on each loop iteration of long packet sequences which causes
- // a performance problem as we spend large amounts of time copying and in gc
- newbuf = new char[offst + charsRead + (stateObj.GetPacketSize() * 8)];
- rentedBuff = false;
- }
-
- if (buff != null)
- {
- Buffer.BlockCopy(buff, 0, newbuf, 0, offst * 2);
- if (returnRentedBufferAfterCopy)
- {
- buff.AsSpan(0, offst).Clear();
- ArrayPool.Shared.Return(buff, clearArray: false);
- }
- }
- buff = newbuf;
- newbuf = null;
+ Buffer.BlockCopy(buff, 0, newbuf, 0, offst * 2);
}
- if (charsRead > 0)
+ buff = newbuf;
+ }
+ if (charsRead > 0)
+ {
+ result = TryReadPlpUnicodeCharsChunk(buff, offst, charsRead, stateObj, out charsRead);
+ if (result != TdsOperationStatus.Done)
{
- result = TryReadPlpUnicodeCharsChunk(buff, offst, charsRead, stateObj, out charsRead);
- if (result != TdsOperationStatus.Done)
- {
- return result;
- }
- charsLeft -= charsRead;
- offst += charsRead;
- totalCharsRead += charsRead;
-
- if (writeDataSizeToSnapshot)
- {
- currentPacketId = IncrementSnapshotDataSize(stateObj, restartingDataSizeCount, currentPacketId, charsRead * 2);
- }
+ return result;
}
+ charsLeft -= charsRead;
+ offst += charsRead;
+ totalCharsRead += charsRead;
}
-
- // Special case single byte
- if (
- (stateObj._longlenleft == 1 || partialReadInProgress)
- && (charsLeft > 0)
- )
+ // Special case single byte left
+ if (stateObj._longlenleft == 1 && (charsLeft > 0))
{
- byte b1 = 0;
- byte b2 = 0;
- if (partialReadInProgress)
+ byte b1;
+ result = stateObj.TryReadByte(out b1);
+ if (result != TdsOperationStatus.Done)
{
- partialReadInProgress = false;
- // we're resuming with a partial char in the buffer so we need to load the byte
- // from the char buffer and put it into b1 so we can combine it with the second
- // half later
- b1 = (byte)(buff[offst] & 0x00ff);
+ return result;
}
- else
+ stateObj._longlenleft--;
+ result = stateObj.TryReadPlpLength(false, out _);
+ if (result != TdsOperationStatus.Done)
{
- result = stateObj.TryReadByte(out b1);
- if (result != TdsOperationStatus.Done)
- {
- return result;
- }
- stateObj._longlenleft--;
- if (writeDataSizeToSnapshot)
- {
- // we need to write the single b1 byte to the array because we may run out of data
- // and need to wait for another packet
- buff[offst] = (char)((b1 & 0xff));
- currentPacketId = IncrementSnapshotDataSize(stateObj, restartingDataSizeCount, currentPacketId, 1);
- }
-
- result = stateObj.TryReadPlpLength(false, out _);
- if (result != TdsOperationStatus.Done)
- {
- return result;
- }
- Debug.Assert((stateObj._longlenleft != 0), "ReadPlpUnicodeChars: Odd byte left at the end!");
+ return result;
}
-
+ Debug.Assert((stateObj._longlenleft != 0), "ReadPlpUnicodeChars: Odd byte left at the end!");
+ byte b2;
result = stateObj.TryReadByte(out b2);
if (result != TdsOperationStatus.Done)
{
@@ -13523,11 +13348,6 @@ bool writeDataSizeToSnapshot
charsRead++;
charsLeft--;
totalCharsRead++;
-
- if (writeDataSizeToSnapshot)
- {
- currentPacketId = IncrementSnapshotDataSize(stateObj, restartingDataSizeCount, currentPacketId, 1);
- }
}
if (stateObj._longlenleft == 0)
{
@@ -13540,41 +13360,9 @@ bool writeDataSizeToSnapshot
}
if (stateObj._longlenleft == 0) // Data read complete
- {
break;
- }
}
return TdsOperationStatus.Done;
-
- static int IncrementSnapshotDataSize(TdsParserStateObject stateObj, bool resetting, int previousPacketId, int value)
- {
- int current = 0;
- if (resetting)
- {
- int currentPacketId = stateObj.GetSnapshotPacketID();
- if (previousPacketId == currentPacketId)
- {
- // we have already reset it the first time we saw it so just add normally
- current = stateObj.GetSnapshotDataSize();
- }
- else
- {
- // a packet we haven't seen before, reset the size
- current = 0;
- }
-
- stateObj.SetSnapshotDataSize(current + value);
-
- // return new packetid so next time we see this packet we know it isn't new
- return currentPacketId;
- }
- else
- {
- current = stateObj.GetSnapshotDataSize();
- stateObj.SetSnapshotDataSize(current + value);
- return previousPacketId;
- }
- }
}
internal int ReadPlpAnsiChars(ref char[] buff, int offst, int len, SqlMetaDataPriv metadata, TdsParserStateObject stateObj)
@@ -13761,15 +13549,14 @@ internal ulong PlpBytesTotalLength(TdsParserStateObject stateObj)
+ " _connHandler = {14}\n\t"
+ " _fMARS = {15}\n\t"
+ " _sessionPool = {16}\n\t"
- + " _sniSpnBuffer = {17}\n\t"
- + " _errors = {18}\n\t"
- + " _warnings = {19}\n\t"
- + " _attentionErrors = {20}\n\t"
- + " _attentionWarnings = {21}\n\t"
- + " _statistics = {22}\n\t"
- + " _statisticsIsInTransaction = {23}\n\t"
- + " _fPreserveTransaction = {24}"
- + " _fParallel = {25}"
+ + " _errors = {17}\n\t"
+ + " _warnings = {18}\n\t"
+ + " _attentionErrors = {19}\n\t"
+ + " _attentionWarnings = {20}\n\t"
+ + " _statistics = {21}\n\t"
+ + " _statisticsIsInTransaction = {22}\n\t"
+ + " _fPreserveTransaction = {23}"
+ + " _fParallel = {24}"
;
internal string TraceString()
{
@@ -13792,7 +13579,6 @@ internal string TraceString()
_connHandler == null ? "(null)" : _connHandler.ObjectID.ToString((IFormatProvider)null),
_fMARS ? bool.TrueString : bool.FalseString,
_sessionPool == null ? "(null)" : _sessionPool.TraceString(),
- _serverSpn == null ? "(null)" : _serverSpn.Length.ToString((IFormatProvider)null),
_physicalStateObj != null ? "(null)" : _physicalStateObj.ErrorCount.ToString((IFormatProvider)null),
_physicalStateObj != null ? "(null)" : _physicalStateObj.WarningCount.ToString((IFormatProvider)null),
_physicalStateObj != null ? "(null)" : _physicalStateObj.PreAttentionErrorCount.ToString((IFormatProvider)null),
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs
index cf54e0340f..a64afdce80 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs
@@ -259,22 +259,14 @@ private void ReadSniError(TdsParserStateObject stateObj, uint error)
stateObj.SendAttention(mustTakeWriteLock: true);
PacketHandle syncReadPacket = default;
- bool readFromNetwork = true;
RuntimeHelpers.PrepareConstrainedRegions();
bool shouldDecrement = false;
try
{
Interlocked.Increment(ref _readingCount);
shouldDecrement = true;
- readFromNetwork = !PartialPacketContainsCompletePacket();
- if (readFromNetwork)
- {
- syncReadPacket = ReadSyncOverAsync(stateObj.GetTimeoutRemaining(), out error);
- }
- else
- {
- error = TdsEnums.SNI_SUCCESS;
- }
+
+ syncReadPacket = ReadSyncOverAsync(stateObj.GetTimeoutRemaining(), out error);
Interlocked.Decrement(ref _readingCount);
shouldDecrement = false;
@@ -287,7 +279,7 @@ private void ReadSniError(TdsParserStateObject stateObj, uint error)
}
else
{
- Debug.Assert(!readFromNetwork || !IsValidPacket(syncReadPacket), "unexpected syncReadPacket without corresponding SNIPacketRelease");
+ Debug.Assert(!IsValidPacket(syncReadPacket), "unexpected syncReadPacket without corresponding SNIPacketRelease");
fail = true; // Subsequent read failed, time to give up.
}
}
@@ -298,7 +290,7 @@ private void ReadSniError(TdsParserStateObject stateObj, uint error)
Interlocked.Decrement(ref _readingCount);
}
- if (readFromNetwork && !IsPacketEmpty(syncReadPacket))
+ if (!IsPacketEmpty(syncReadPacket))
{
// Be sure to release packet, otherwise it will be leaked by native.
ReleasePacket(syncReadPacket);
@@ -339,9 +331,60 @@ private void ReadSniError(TdsParserStateObject stateObj, uint error)
AssertValidState();
}
- private uint GetSniPacket(PacketHandle packet, ref uint dataSize)
+ public void ProcessSniPacket(PacketHandle packet, uint error)
{
- return SniPacketGetData(packet, _inBuff, ref dataSize);
+ if (error != 0)
+ {
+ if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken))
+ {
+ // Do nothing with callback if closed or broken and error not 0 - callback can occur
+ // after connection has been closed. PROBLEM IN NETLIB - DESIGN FLAW.
+ return;
+ }
+
+ AddError(_parser.ProcessSNIError(this));
+ AssertValidState();
+ }
+ else
+ {
+ uint dataSize = 0;
+
+ uint getDataError = SniPacketGetData(packet, _inBuff, ref dataSize);
+
+ if (getDataError == TdsEnums.SNI_SUCCESS)
+ {
+ if (_inBuff.Length < dataSize)
+ {
+ Debug.Assert(true, "Unexpected dataSize on Read");
+ throw SQL.InvalidInternalPacketSize(StringsHelper.GetString(Strings.SqlMisc_InvalidArraySizeMessage));
+ }
+
+ _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks;
+ _inBytesRead = (int)dataSize;
+ _inBytesUsed = 0;
+
+ if (_snapshot != null)
+ {
+ _snapshot.AppendPacketData(_inBuff, _inBytesRead);
+ if (_snapshotReplay)
+ {
+ _snapshot.MoveNext();
+#if DEBUG
+ _snapshot.AssertCurrent();
+#endif
+ }
+ }
+
+ SniReadStatisticsAndTracing();
+ SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParser.ReadNetworkPacketAsyncCallback | INFO | ADV | State Object Id {0}, Packet read. In Buffer: {1}, In Bytes Read: {2}", ObjectID, _inBuff, _inBytesRead);
+
+ AssertValidState();
+ }
+ else
+ {
+ throw SQL.ParsingError(ParsingErrorState.ProcessSniPacketFailed);
+ }
+ }
}
public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error)
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.netfx.cs
index 941e3325b6..af7269ed8b 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.netfx.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.netfx.cs
@@ -5,8 +5,6 @@
using System;
using System.Data.SqlTypes;
using System.Reflection;
-using System.Reflection.Emit;
-using System.Runtime.Serialization;
using Microsoft.Data.SqlClient;
namespace Microsoft.Data.SqlTypes
@@ -20,7 +18,10 @@ namespace Microsoft.Data.SqlTypes
internal static partial class SqlTypeWorkarounds
{
#region Work around inability to access SqlMoney.ctor(long, int) and SqlMoney.ToSqlInternalRepresentation
- private static readonly Func s_sqlMoneyfactory = CtorHelper.CreateFactory(); // binds to SqlMoney..ctor(long, int) if it exists
+ // Documentation for internal ctor:
+ // https://learn.microsoft.com/en-us/dotnet/framework/additional-apis/system.data.sqltypes.sqlmoney.-ctor
+ private static readonly Func s_sqlMoneyfactory =
+ CtorHelper.CreateFactory(); // binds to SqlMoney..ctor(long, int) if it exists
///
/// Constructs a SqlMoney from a long value without scaling. The ignored parameter exists
@@ -70,6 +71,11 @@ internal static SqlMoneyToLongDelegate GetSqlMoneyToLong()
private static SqlMoneyToLongDelegate GetFastSqlMoneyToLong()
{
+ // Note: Although it would be faster to use the m_value member variable in
+ // SqlMoney, but because it is not documented, we cannot use it. The method
+ // we are calling below *is* documented, despite it being internal.
+ // Documentation for internal method:
+ // https://learn.microsoft.com/en-us/dotnet/framework/additional-apis/system.data.sqltypes.sqlmoney.tosqlinternalrepresentation
MethodInfo toSqlInternalRepresentation = typeof(SqlMoney).GetMethod("ToSqlInternalRepresentation",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.ExactBinding,
null, CallingConventions.Any, new Type[] { }, null);
@@ -113,145 +119,45 @@ private static long FallbackSqlMoneyToLong(ref SqlMoney value)
}
#endregion
- #region Work around inability to access SqlDecimal._data1/2/3/4
- internal static void SqlDecimalExtractData(SqlDecimal d, out uint data1, out uint data2, out uint data3, out uint data4)
- {
- SqlDecimalHelper.s_decompose(d, out data1, out data2, out data3, out data4);
- }
+ #region Work around SqlDecimal.WriteTdsValue not existing in netfx
- private static class SqlDecimalHelper
+ ///
+ /// Implementation that mimics netcore's WriteTdsValue method.
+ ///
+ ///
+ /// Although calls to this method could just be replaced with calls to
+ /// , using this mimic method allows netfx and netcore
+ /// implementations to be more cleanly switched.
+ ///
+ /// SqlDecimal value to get data from.
+ /// First data field will be written here.
+ /// Second data field will be written here.
+ /// Third data field will be written here.
+ /// Fourth data field will be written here.
+ internal static void SqlDecimalExtractData(
+ SqlDecimal value,
+ out uint data1,
+ out uint data2,
+ out uint data3,
+ out uint data4)
{
- internal delegate void Decomposer(SqlDecimal value, out uint data1, out uint data2, out uint data3, out uint data4);
- internal static readonly Decomposer s_decompose = GetDecomposer();
-
- private static Decomposer GetDecomposer()
- {
- Decomposer decomposer = null;
- try
- {
- decomposer = GetFastDecomposer();
- }
- catch
- {
- // If an exception occurs for any reason, swallow & use the fallback code path.
- }
-
- return decomposer ?? FallbackDecomposer;
- }
-
- private static Decomposer GetFastDecomposer()
- {
- // This takes advantage of the fact that for [Serializable] types, the member fields are implicitly
- // part of the type's serialization contract. This includes the fields' names and types. By default,
- // [Serializable]-compliant serializers will read all the member fields and shove the data into a
- // SerializationInfo dictionary. We mimic this behavior in a manner consistent with the [Serializable]
- // pattern, but much more efficiently.
- //
- // In order to make sure we're staying compliant, we need to gate our checks to fulfill some core
- // assumptions. Importantly, the type must be [Serializable] but cannot be ISerializable, as the
- // presence of the interface means that the type wants to be responsible for its own serialization,
- // and that member fields are not guaranteed to be part of the serialization contract. Additionally,
- // we need to check for [OnSerializing] and [OnDeserializing] methods, because we cannot account
- // for any logic which might be present within them.
-
- if (!typeof(SqlDecimal).IsSerializable)
- {
- SqlClientEventSource.Log.TryTraceEvent("SqlTypeWorkarounds.SqlDecimalHelper.GetFastDecomposer | Info | SqlDecimal isn't Serializable. Less efficient fallback method will be used.");
- return null; // type is not serializable - cannot use fast path assumptions
- }
-
- if (typeof(ISerializable).IsAssignableFrom(typeof(SqlDecimal)))
- {
- SqlClientEventSource.Log.TryTraceEvent("SqlTypeWorkarounds.SqlDecimalHelper.GetFastDecomposer | Info | SqlDecimal is ISerializable. Less efficient fallback method will be used.");
- return null; // type contains custom logic - cannot use fast path assumptions
- }
-
- foreach (MethodInfo method in typeof(SqlDecimal).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
- {
- if (method.IsDefined(typeof(OnDeserializingAttribute)) || method.IsDefined(typeof(OnDeserializedAttribute)))
- {
- SqlClientEventSource.Log.TryTraceEvent("SqlTypeWorkarounds.SqlDecimalHelper.GetFastDecomposer | Info | SqlDecimal contains custom serialization logic. Less efficient fallback method will be used.");
- return null; // type contains custom logic - cannot use fast path assumptions
- }
- }
-
- // GetSerializableMembers filters out [NonSerialized] fields for us automatically.
-
- FieldInfo fiData1 = null, fiData2 = null, fiData3 = null, fiData4 = null;
- foreach (MemberInfo candidate in FormatterServices.GetSerializableMembers(typeof(SqlDecimal)))
- {
- if (candidate is FieldInfo fi && fi.FieldType == typeof(uint))
- {
- if (fi.Name == "m_data1")
- { fiData1 = fi; }
- else if (fi.Name == "m_data2")
- { fiData2 = fi; }
- else if (fi.Name == "m_data3")
- { fiData3 = fi; }
- else if (fi.Name == "m_data4")
- { fiData4 = fi; }
- }
- }
-
- if (fiData1 is null || fiData2 is null || fiData3 is null || fiData4 is null)
- {
- SqlClientEventSource.Log.TryTraceEvent("SqlTypeWorkarounds.SqlDecimalHelper.GetFastDecomposer | Info | Expected SqlDecimal fields are missing. Less efficient fallback method will be used.");
- return null; // missing one of the expected member fields - cannot use fast path assumptions
- }
-
- Type refToUInt32 = typeof(uint).MakeByRefType();
- DynamicMethod dm = new(
- name: "sqldecimal-decomposer",
- returnType: typeof(void),
- parameterTypes: new[] { typeof(SqlDecimal), refToUInt32, refToUInt32, refToUInt32, refToUInt32 },
- restrictedSkipVisibility: true); // perf: JITs method at delegate creation time
-
- ILGenerator ilGen = dm.GetILGenerator();
- ilGen.Emit(OpCodes.Ldarg_1); // eval stack := [UInt32&]
- ilGen.Emit(OpCodes.Ldarg_0); // eval stack := [UInt32&] [SqlDecimal]
- ilGen.Emit(OpCodes.Ldfld, fiData1); // eval stack := [UInt32&] [UInt32]
- ilGen.Emit(OpCodes.Stind_I4); // eval stack :=
- ilGen.Emit(OpCodes.Ldarg_2); // eval stack := [UInt32&]
- ilGen.Emit(OpCodes.Ldarg_0); // eval stack := [UInt32&] [SqlDecimal]
- ilGen.Emit(OpCodes.Ldfld, fiData2); // eval stack := [UInt32&] [UInt32]
- ilGen.Emit(OpCodes.Stind_I4); // eval stack :=
- ilGen.Emit(OpCodes.Ldarg_3); // eval stack := [UInt32&]
- ilGen.Emit(OpCodes.Ldarg_0); // eval stack := [UInt32&] [SqlDecimal]
- ilGen.Emit(OpCodes.Ldfld, fiData3); // eval stack := [UInt32&] [UInt32]
- ilGen.Emit(OpCodes.Stind_I4); // eval stack :=
- ilGen.Emit(OpCodes.Ldarg_S, (byte)4); // eval stack := [UInt32&]
- ilGen.Emit(OpCodes.Ldarg_0); // eval stack := [UInt32&] [SqlDecimal]
- ilGen.Emit(OpCodes.Ldfld, fiData4); // eval stack := [UInt32&] [UInt32]
- ilGen.Emit(OpCodes.Stind_I4); // eval stack :=
- ilGen.Emit(OpCodes.Ret);
-
- return (Decomposer)dm.CreateDelegate(typeof(Decomposer), null /* target */);
- }
-
- // Used in case we can't use a [Serializable]-like mechanism.
- private static void FallbackDecomposer(SqlDecimal value, out uint data1, out uint data2, out uint data3, out uint data4)
- {
- if (value.IsNull)
- {
- data1 = default;
- data2 = default;
- data3 = default;
- data4 = default;
- }
- else
- {
- int[] data = value.Data; // allocation
- data4 = (uint)data[3]; // write in reverse to avoid multiple bounds checks
- data3 = (uint)data[2];
- data2 = (uint)data[1];
- data1 = (uint)data[0];
- }
- }
+ // Note: Although it would be faster to use the m_data[1-4] member variables in
+ // SqlDecimal, we cannot use them because they are not documented. The Data property
+ // is less ideal, but is documented.
+ int[] data = value.Data;
+ data1 = (uint)data[0];
+ data2 = (uint)data[1];
+ data3 = (uint)data[2];
+ data4 = (uint)data[3];
}
+
#endregion
#region Work around inability to access SqlBinary.ctor(byte[], bool)
- private static readonly Func s_sqlBinaryfactory = CtorHelper.CreateFactory(); // binds to SqlBinary..ctor(byte[], bool) if it exists
+ // Documentation of internal constructor:
+ // https://learn.microsoft.com/en-us/dotnet/framework/additional-apis/system.data.sqltypes.sqlbinary.-ctor
+ private static readonly Func s_sqlBinaryfactory =
+ CtorHelper.CreateFactory();
internal static SqlBinary SqlBinaryCtor(byte[] value, bool ignored)
{
@@ -270,7 +176,10 @@ internal static SqlBinary SqlBinaryCtor(byte[] value, bool ignored)
#endregion
#region Work around inability to access SqlGuid.ctor(byte[], bool)
- private static readonly Func s_sqlGuidfactory = CtorHelper.CreateFactory(); // binds to SqlGuid..ctor(byte[], bool) if it exists
+ // Documentation for internal constructor:
+ // https://learn.microsoft.com/en-us/dotnet/framework/additional-apis/system.data.sqltypes.sqlguid.-ctor
+ private static readonly Func s_sqlGuidfactory =
+ CtorHelper.CreateFactory();
internal static SqlGuid SqlGuidCtor(byte[] value, bool ignored)
{
diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniError.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniError.cs
index b349c2876d..2e519c15ab 100644
--- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniError.cs
+++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniError.cs
@@ -12,7 +12,7 @@ internal struct SniError
internal Provider provider;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 261)]
internal string errorMessage;
- internal uint nativeError;
+ internal int nativeError;
internal uint sniError;
[MarshalAs(UnmanagedType.LPWStr)]
internal string fileName;
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj
index ff8274c66f..3a1d19a96d 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj
@@ -8,8 +8,14 @@
+
+
+
+
+
+
@@ -17,12 +23,7 @@
-
-
-
-
-
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
index 470cb8e35e..928dc6bab6 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
@@ -24,6 +24,7 @@
using Microsoft.Identity.Client;
using Microsoft.SqlServer.Server;
using System.Security.Authentication;
+using System.Collections.Generic;
#if NETFRAMEWORK
using System.Reflection;
@@ -617,6 +618,13 @@ internal static Delegate FindBuilder(MulticastDelegate mcd)
internal static long TimerCurrent() => DateTime.UtcNow.ToFileTimeUtc();
+ internal static long FastTimerCurrent() => Environment.TickCount;
+
+ internal static uint CalculateTickCountElapsed(long startTick, long endTick)
+ {
+ return (uint)(endTick - startTick);
+ }
+
internal static long TimerFromSeconds(int seconds)
{
long result = checked((long)seconds * TimeSpan.TicksPerSecond);
@@ -768,7 +776,7 @@ internal static Version GetAssemblyVersion()
/// This array includes endpoint URLs for Azure SQL in global, Germany, US Government,
/// China, and Fabric environments. These endpoints are used to identify and interact with Azure SQL services
/// in their respective regions or environments.
- internal static readonly string[] s_azureSqlServerEndpoints = { AZURE_SQL,
+ internal static readonly List s_azureSqlServerEndpoints = new() { AZURE_SQL,
AZURE_SQL_GERMANY,
AZURE_SQL_USGOV,
AZURE_SQL_CHINA,
@@ -808,7 +816,7 @@ internal static bool IsAzureSqlServerEndpoint(string dataSource)
}
// This method assumes dataSource parameter is in TCP connection string format.
- private static bool IsEndpoint(string dataSource, string[] endpoints)
+ private static bool IsEndpoint(string dataSource, ICollection endpoints)
{
int length = dataSource.Length;
// remove server port
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringDefaults.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringDefaults.cs
index 41a8058b5c..ca5ebb2226 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringDefaults.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringDefaults.cs
@@ -57,7 +57,7 @@ internal static class DbConnectionStringDefaults
#if NETFRAMEWORK
internal const bool ConnectionReset = true;
- internal static readonly bool TransparentNetworkIPResolution = !LocalAppContextSwitches.DisableTnirByDefault;
+ internal static bool TransparentNetworkIpResolution => !LocalAppContextSwitches.DisableTnirByDefault;
internal const string NetworkLibrary = "";
#endif
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs
index d4ea183312..f4e87010a6 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs
@@ -32,6 +32,7 @@ internal override void CloseConnection(DbConnection owningObject, DbConnectionFa
// not much to do here...
}
+ ///
protected override void Deactivate() => ADP.ClosedConnectionError();
public override void EnlistTransaction(System.Transactions.Transaction transaction) => throw ADP.ClosedConnectionError();
@@ -43,6 +44,9 @@ protected internal override DataTable GetSchema(DbConnectionFactory factory, DbC
internal override bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions)
=> base.TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions);
+
+ ///
+ internal override void ResetConnection() => throw ADP.ClosedConnectionError();
}
internal abstract class DbConnectionBusy : DbConnectionClosed
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs
index b4dfb4f214..eafddd8ae2 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs
@@ -797,6 +797,13 @@ internal void PrePush(object expectedOwner)
internal void RemoveWeakReference(object value) =>
ReferenceCollection?.Remove(value);
+ ///
+ /// Idempotently resets the connection so that it may be recycled without leaking state.
+ /// May preserve transaction state if the connection is enlisted in a distributed transaction.
+ /// Should be called before the first action is taken on a recycled connection.
+ ///
+ internal abstract void ResetConnection();
+
internal void SetInStasis()
{
IsTxRootWaitingForTxEnd = true;
@@ -834,6 +841,11 @@ internal virtual bool TryReplaceConnection(
#region Protected Methods
+ ///
+ /// Activates the connection, preparing it for active use.
+ /// An activated connection has an owner and is checked out from the connection pool (if pooling is enabled).
+ ///
+ /// The transaction in which the connection should enlist.
protected abstract void Activate(Transaction transaction);
///
@@ -850,6 +862,11 @@ protected virtual DbReferenceCollection CreateReferenceCollection()
throw ADP.InternalError(ADP.InternalErrorCode.AttemptingToConstructReferenceCollectionOnStaticObject);
}
+ ///
+ /// Deactivates the connection, cleaning up any state as necessary.
+ /// A deactivated connection is one that is no longer in active use and does not have an owner.
+ /// A deactivated connection may be open (connected to a server) and is checked into the connection pool (if pooling is enabled).
+ ///
protected abstract void Deactivate();
protected internal void DoNotPoolThisConnection()
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs
index b374a550b3..9cbec9516d 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs
@@ -223,7 +223,11 @@ public override async Task AcquireTokenAsync(SqlAuthenti
{
if (!string.IsNullOrEmpty(parameters.UserId))
{
+ // The AcquireTokenByIntegratedWindowsAuth method is marked as obsolete in MSAL.NET
+ // but it is still a supported way to acquire tokens for Active Directory Integrated authentication.
+#pragma warning disable CS0618 // Type or member is obsolete
result = await app.AcquireTokenByIntegratedWindowsAuth(scopes)
+#pragma warning restore CS0618 // Type or member is obsolete
.WithCorrelationId(parameters.ConnectionId)
.WithUsername(parameters.UserId)
.ExecuteAsync(cancellationToken: cts.Token)
@@ -231,7 +235,9 @@ public override async Task AcquireTokenAsync(SqlAuthenti
}
else
{
+#pragma warning disable CS0618 // Type or member is obsolete
result = await app.AcquireTokenByIntegratedWindowsAuth(scopes)
+#pragma warning restore CS0618 // Type or member is obsolete
.WithCorrelationId(parameters.ConnectionId)
.ExecuteAsync(cancellationToken: cts.Token)
.ConfigureAwait(false);
@@ -582,7 +588,28 @@ private static TokenCredentialData CreateTokenCredentialInstance(TokenCredential
defaultAzureCredentialOptions.WorkloadIdentityClientId = tokenCredentialKey._clientId;
}
- return new TokenCredentialData(new DefaultAzureCredential(defaultAzureCredentialOptions), GetHash(secret));
+ // SqlClient is a library and provides support to acquire access
+ // token using 'DefaultAzureCredential' on user demand when they
+ // specify 'Authentication = Active Directory Default' in
+ // connection string.
+ //
+ // Default Azure Credential is instantiated by the calling
+ // application when using "Active Directory Default"
+ // authentication code to connect to Azure SQL instance.
+ // SqlClient is a library, doesn't instantiate the credential
+ // without running application instructions.
+ //
+ // Note that CodeQL suppression support can only detect
+ // suppression comments that appear immediately above the
+ // flagged statement, or appended to the end of the statement.
+ // Multi-line justifications are not supported.
+ //
+ // https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-docs/codeql/codeql-semmle#guidance-on-suppressions
+ //
+ // CodeQL [SM05137] See above for justification.
+ DefaultAzureCredential cred = new(defaultAzureCredentialOptions);
+
+ return new TokenCredentialData(cred, GetHash(secret));
}
TokenCredentialOptions tokenCredentialOptions = new() { AuthorityHost = new Uri(tokenCredentialKey._authority) };
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs
index 77d2b3bae1..cb099e27d3 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs
@@ -382,7 +382,6 @@ internal WaitHandle[] GetHandles(bool withCreate)
return withCreate ? _handlesWithCreate : _handlesWithoutCreate;
}
}
-
private const int MAX_Q_SIZE = 0x00100000;
// The order of these is important; we want the WaitAny call to be signaled
@@ -1321,32 +1320,36 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj
if (onlyOneCheckConnection)
{
- if (_waitHandles.CreationSemaphore.WaitOne(unchecked((int)waitForMultipleObjectsTimeout)))
- {
+ bool obtained = false;
#if NETFRAMEWORK
- RuntimeHelpers.PrepareConstrainedRegions();
+ RuntimeHelpers.PrepareConstrainedRegions();
#endif
- try
+ try
+ {
+ obtained = _waitHandles.CreationSemaphore.WaitOne(unchecked((int)waitForMultipleObjectsTimeout));
+ if (obtained)
{
SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", Id);
obj = UserCreateRequest(owningObject, userOptions);
}
- finally
+ else
{
- _waitHandles.CreationSemaphore.Release(1);
+ // Timeout waiting for creation semaphore - return null
+ SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Wait timed out.", Id);
+ connection = null;
+ return false;
}
}
- else
+ finally
{
- // Timeout waiting for creation semaphore - return null
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Wait timed out.", Id);
- connection = null;
- return false;
+ if (obtained)
+ {
+ _waitHandles.CreationSemaphore.Release(1);
+ }
}
}
}
break;
-
case WAIT_ABANDONED + SEMAPHORE_HANDLE:
SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Semaphore handle abandonded.", Id);
Interlocked.Decrement(ref _waitCount);
@@ -1553,25 +1556,16 @@ private void PoolCreateRequest(object state)
{
return;
}
- int waitResult = BOGUS_HANDLE;
-
#if NETFRAMEWORK
RuntimeHelpers.PrepareConstrainedRegions();
#endif
+ bool obtained = false;
try
{
// Obtain creation mutex so we're the only one creating objects
- // and we must have the wait result
-#if NETFRAMEWORK
- RuntimeHelpers.PrepareConstrainedRegions();
-#endif
- try
- { }
- finally
- {
- waitResult = WaitHandle.WaitAny(_waitHandles.GetHandles(withCreate: true), CreationTimeout);
- }
- if (CREATION_HANDLE == waitResult)
+ obtained = _waitHandles.CreationSemaphore.WaitOne(CreationTimeout);
+
+ if (obtained)
{
DbConnectionInternal newObj;
@@ -1606,17 +1600,12 @@ private void PoolCreateRequest(object state)
}
}
}
- else if (WaitHandle.WaitTimeout == waitResult)
+ else
{
// do not wait forever and potential block this worker thread
// instead wait for a period of time and just requeue to try again
QueuePoolCreateRequest();
}
- else
- {
- // trace waitResult and ignore the failure
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, PoolCreateRequest called WaitForSingleObject failed {1}", Id, waitResult);
- }
}
catch (Exception e)
{
@@ -1632,9 +1621,8 @@ private void PoolCreateRequest(object state)
}
finally
{
- if (CREATION_HANDLE == waitResult)
+ if (obtained)
{
- // reuse waitResult and ignore its value
_waitHandles.CreationSemaphore.Release(1);
}
}
@@ -1694,8 +1682,6 @@ public void PutObjectFromTransactedPool(DbConnectionInternal obj)
Debug.Assert(obj != null, "null pooledObject?");
Debug.Assert(obj.EnlistedTransaction == null, "pooledObject is still enlisted?");
- obj.DeactivateConnection();
-
// called by the transacted connection pool , once it's removed the
// connection from it's list. We put the connection back in general
// circulation.
@@ -1708,6 +1694,7 @@ public void PutObjectFromTransactedPool(DbConnectionInternal obj)
if (State is Running && obj.CanBePooled)
{
+ obj.ResetConnection();
PutNewObject(obj);
}
else
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs
index f62c65b122..9f49080361 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs
@@ -92,7 +92,7 @@ internal sealed partial class SqlClientMetrics
private string? _instanceName;
#endif
- public SqlClientMetrics(SqlClientEventSource eventSource)
+ public SqlClientMetrics(SqlClientEventSource eventSource, bool enableMetrics)
{
_eventSource = eventSource;
@@ -100,6 +100,11 @@ public SqlClientMetrics(SqlClientEventSource eventSource)
// On .NET Framework, metrics are exposed as performance counters and are always enabled.
// On .NET Core, metrics are exposed as EventCounters, and require explicit enablement.
EnablePerformanceCounters();
+#else
+ if (enableMetrics)
+ {
+ EnableEventCounters();
+ }
#endif
}
@@ -489,25 +494,25 @@ private void RemovePerformanceCounters()
private PerformanceCounter? CreatePerformanceCounter(string counterName, PerformanceCounterType counterType)
{
- PerformanceCounter? instance = null;
-
_instanceName ??= GetInstanceName();
try
{
- instance = new PerformanceCounter();
+ PerformanceCounter instance = new();
instance.CategoryName = PerformanceCounterCategoryName;
instance.CounterName = counterName;
instance.InstanceName = _instanceName;
instance.InstanceLifetime = PerformanceCounterInstanceLifetime.Process;
instance.ReadOnly = false;
instance.RawValue = 0; // make sure we start out at zero
+
+ return instance;
}
catch (InvalidOperationException e)
{
ADP.TraceExceptionWithoutRethrow(e);
- }
- return instance;
+ return null;
+ }
}
// SxS: this method uses GetCurrentProcessId to construct the instance name.
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs
index dfd94453f2..702be5f842 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs
@@ -20,9 +20,8 @@ private enum Tristate : byte
internal const string SuppressInsecureTlsWarningString = @"Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning";
internal const string UseMinimumLoginTimeoutString = @"Switch.Microsoft.Data.SqlClient.UseOneSecFloorInTimeoutCalculationDuringLogin";
internal const string LegacyVarTimeZeroScaleBehaviourString = @"Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour";
- internal const string UseCompatibilityProcessSniString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni";
- internal const string UseCompatibilityAsyncBehaviourString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour";
internal const string UseConnectionPoolV2String = @"Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2";
+ private const string IgnoreServerProvidedFailoverPartnerString = @"Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner";
// this field is accessed through reflection in tests and should not be renamed or have the type changed without refactoring NullRow related tests
private static Tristate s_legacyRowVersionNullBehavior;
@@ -31,9 +30,9 @@ private enum Tristate : byte
private static Tristate s_useMinimumLoginTimeout;
// this field is accessed through reflection in Microsoft.Data.SqlClient.Tests.SqlParameterTests and should not be renamed or have the type changed without refactoring related tests
private static Tristate s_legacyVarTimeZeroScaleBehaviour;
- private static Tristate s_useCompatibilityProcessSni;
- private static Tristate s_useCompatibilityAsyncBehaviour;
private static Tristate s_useConnectionPoolV2;
+ private static Tristate s_ignoreServerProvidedFailoverPartner;
+
#if NET
static LocalAppContextSwitches()
@@ -89,66 +88,6 @@ public static bool DisableTnirByDefault
}
}
#endif
- ///
- /// In TdsParser the ProcessSni function changed significantly when the packet
- /// multiplexing code needed for high speed multi-packet column values was added.
- /// In case of compatibility problems this switch will change TdsParser to use
- /// the previous version of the function.
- ///
- public static bool UseCompatibilityProcessSni
- {
- get
- {
- if (s_useCompatibilityProcessSni == Tristate.NotInitialized)
- {
- if (AppContext.TryGetSwitch(UseCompatibilityProcessSniString, out bool returnedValue) && returnedValue)
- {
- s_useCompatibilityProcessSni = Tristate.True;
- }
- else
- {
- s_useCompatibilityProcessSni = Tristate.False;
- }
- }
- return s_useCompatibilityProcessSni == Tristate.True;
- }
- }
-
- ///
- /// In TdsParser the async multi-packet column value fetch behaviour is capable of
- /// using a continue snapshot state in addition to the original replay from start
- /// logic.
- /// This switch disables use of the continue snapshot state. This switch will always
- /// return true if is enabled because the
- /// continue state is not stable without the multiplexer.
- ///
- public static bool UseCompatibilityAsyncBehaviour
- {
- get
- {
- if (UseCompatibilityProcessSni)
- {
- // If ProcessSni compatibility mode has been enabled then the packet
- // multiplexer has been disabled. The new async behaviour using continue
- // point capture is only stable if the multiplexer is enabled so we must
- // return true to enable compatibility async behaviour using only restarts.
- return true;
- }
-
- if (s_useCompatibilityAsyncBehaviour == Tristate.NotInitialized)
- {
- if (AppContext.TryGetSwitch(UseCompatibilityAsyncBehaviourString, out bool returnedValue) && returnedValue)
- {
- s_useCompatibilityAsyncBehaviour = Tristate.True;
- }
- else
- {
- s_useCompatibilityAsyncBehaviour = Tristate.False;
- }
- }
- return s_useCompatibilityAsyncBehaviour == Tristate.True;
- }
- }
///
/// When using Encrypt=false in the connection string, a security warning is output to the console if the TLS version is 1.2 or lower.
@@ -251,7 +190,7 @@ public static bool UseMinimumLoginTimeout
/// When set to 'true' this will output a scale value of 7 (DEFAULT_VARTIME_SCALE) when the scale
/// is explicitly set to zero for VarTime data types ('datetime2', 'datetimeoffset' and 'time')
/// If no scale is set explicitly it will continue to output scale of 7 (DEFAULT_VARTIME_SCALE)
- /// regardsless of switch value.
+ /// regardless of switch value.
/// This app context switch defaults to 'true'.
///
public static bool LegacyVarTimeZeroScaleBehaviour
@@ -296,5 +235,33 @@ public static bool UseConnectionPoolV2
return s_useConnectionPoolV2 == Tristate.True;
}
}
+
+ ///
+ /// When set to true, the failover partner provided by the server during connection
+ /// will be ignored. This is useful in scenarios where the application wants to
+ /// control the failover behavior explicitly (e.g. using a custom port). The application
+ /// must be kept up to date with the failover configuration of the server.
+ /// The application will not automatically discover a newly configured failover partner.
+ ///
+ /// This app context switch defaults to 'false'.
+ ///
+ public static bool IgnoreServerProvidedFailoverPartner
+ {
+ get
+ {
+ if (s_ignoreServerProvidedFailoverPartner == Tristate.NotInitialized)
+ {
+ if (AppContext.TryGetSwitch(IgnoreServerProvidedFailoverPartnerString, out bool returnedValue) && returnedValue)
+ {
+ s_ignoreServerProvidedFailoverPartner = Tristate.True;
+ }
+ else
+ {
+ s_ignoreServerProvidedFailoverPartner = Tristate.False;
+ }
+ }
+ return s_ignoreServerProvidedFailoverPartner == Tristate.True;
+ }
+ }
}
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/ResolvedServerSpn.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/ResolvedServerSpn.cs
new file mode 100644
index 0000000000..fe49c77f6b
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/ResolvedServerSpn.cs
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#nullable enable
+
+namespace Microsoft.Data.SqlClient.ManagedSni
+{
+ ///
+ /// This is used to hold the ServerSpn for a given connection. Most connection types have a single format, although TCP connections may allow
+ /// with and without a port. Depending on how the SPN is registered on the server, either one may be the correct name.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// SQL Server SPN format follows these patterns:
+ ///
+ /// -
+ /// Default instance, no port (primary):
+ /// MSSQLSvc/fully-qualified-domain-name
+ ///
+ /// -
+ /// Default instance, default port (secondary):
+ /// MSSQLSvc/fully-qualified-domain-name:1433
+ ///
+ /// -
+ /// Named instance or custom port:
+ /// MSSQLSvc/fully-qualified-domain-name:port_or_instance_name
+ ///
+ ///
+ /// For TCP connections to named instances, the port number is used in SPN.
+ /// For Named Pipe connections to named instances, the instance name is used in SPN.
+ /// When hostname resolution fails, the user-provided hostname is used instead of FQDN.
+ /// For default instances with TCP protocol, both forms (with and without port) may be returned.
+ ///
+ internal readonly struct ResolvedServerSpn(string primary, string? secondary = null)
+ {
+ public string Primary => primary;
+
+ public string? Secondary => secondary;
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniCommon.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniCommon.netcore.cs
index 6aaf23f877..9653c94d34 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniCommon.netcore.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniCommon.netcore.cs
@@ -189,7 +189,7 @@ internal static IPAddress[] GetDnsIpAddresses(string serverName)
/// SNI error code
/// Error message
///
- internal static uint ReportSNIError(SniProviders provider, uint nativeError, uint sniError, string errorMessage)
+ internal static uint ReportSNIError(SniProviders provider, int nativeError, uint sniError, string errorMessage)
{
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniCommon), EventType.ERR, "Provider = {0}, native Error = {1}, SNI Error = {2}, Error Message = {3}", args0: provider, args1: nativeError, args2: sniError, args3: errorMessage);
return ReportSNIError(new SniError(provider, nativeError, sniError, errorMessage));
@@ -203,7 +203,7 @@ internal static uint ReportSNIError(SniProviders provider, uint nativeError, uin
/// SNI Exception
/// Native SNI error code
///
- internal static uint ReportSNIError(SniProviders provider, uint sniError, Exception sniException, uint nativeErrorCode = 0)
+ internal static uint ReportSNIError(SniProviders provider, uint sniError, Exception sniException, int nativeErrorCode = 0)
{
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniCommon), EventType.ERR, "Provider = {0}, SNI Error = {1}, Exception = {2}", args0: provider, args1: sniError, args2: sniException?.Message);
return ReportSNIError(new SniError(provider, sniError, sniException, nativeErrorCode));
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniError.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniError.netcore.cs
index 1a33de1d96..9cecb1e70f 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniError.netcore.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniError.netcore.cs
@@ -5,6 +5,8 @@
#if NET
using System;
+using System.ComponentModel;
+using System.Net.Sockets;
namespace Microsoft.Data.SqlClient.ManagedSni
{
@@ -14,17 +16,18 @@ namespace Microsoft.Data.SqlClient.ManagedSni
internal class SniError
{
// Error numbers from native SNI implementation
- internal const uint CertificateValidationErrorCode = 2148074277;
+ // This is signed int representation of the error code 0x80090325
+ internal const int CertificateValidationErrorCode = -2146893019;
public readonly SniProviders provider;
public readonly string errorMessage;
- public readonly uint nativeError;
+ public readonly int nativeError;
public readonly uint sniError;
public readonly string function;
public readonly uint lineNumber;
public readonly Exception exception;
- public SniError(SniProviders provider, uint nativeError, uint sniErrorCode, string errorMessage)
+ public SniError(SniProviders provider, int nativeError, uint sniErrorCode, string errorMessage)
{
lineNumber = 0;
function = string.Empty;
@@ -35,12 +38,25 @@ public SniError(SniProviders provider, uint nativeError, uint sniErrorCode, stri
exception = null;
}
- public SniError(SniProviders provider, uint sniErrorCode, Exception sniException, uint nativeErrorCode = 0)
+ public SniError(SniProviders provider, uint sniErrorCode, Exception sniException, int nativeErrorCode = 0)
{
lineNumber = 0;
function = string.Empty;
this.provider = provider;
nativeError = nativeErrorCode;
+ if (nativeErrorCode == 0)
+ {
+ if (sniException is SocketException socketException)
+ {
+ // SocketErrorCode values are cross-plat consistent in .NET (matching native Windows error codes)
+ // underlying type of SocketErrorCode is int
+ nativeError = (int)socketException.SocketErrorCode;
+ }
+ else if (sniException is Win32Exception win32Exception)
+ {
+ nativeError = win32Exception.NativeErrorCode; // Replicates native SNI behavior
+ }
+ }
sniError = sniErrorCode;
errorMessage = string.Empty;
exception = sniException;
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniNpHandle.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniNpHandle.netcore.cs
index e244209f23..7a1c2ec660 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniNpHandle.netcore.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniNpHandle.netcore.cs
@@ -203,7 +203,7 @@ public override uint Receive(out SniPacket packet, int timeout)
packet = null;
var e = new Win32Exception();
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.ERR, "Connection Id {0}, Packet length found 0, Win32 exception raised: {1}", args0: _connectionId, args1: e?.Message);
- return ReportErrorAndReleasePacket(errorPacket, (uint)e.NativeErrorCode, 0, e.Message);
+ return ReportErrorAndReleasePacket(errorPacket, e.NativeErrorCode, 0, e.Message);
}
}
catch (ObjectDisposedException ode)
@@ -413,7 +413,7 @@ private uint ReportErrorAndReleasePacket(SniPacket packet, Exception sniExceptio
return SniCommon.ReportSNIError(SniProviders.NP_PROV, SniCommon.InternalExceptionError, sniException);
}
- private uint ReportErrorAndReleasePacket(SniPacket packet, uint nativeError, uint sniError, string errorMessage)
+ private uint ReportErrorAndReleasePacket(SniPacket packet, int nativeError, uint sniError, string errorMessage)
{
if (packet != null)
{
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProxy.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProxy.netcore.cs
index c09e5a7b27..817ad1f4f2 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProxy.netcore.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProxy.netcore.cs
@@ -33,7 +33,7 @@ internal class SniProxy
/// Full server name from connection string
/// Timer expiration
/// Instance name
- /// SPNs
+ /// SPN
/// pre-defined SPN
/// Flush packet cache
/// Asynchronous connection
@@ -50,7 +50,7 @@ internal static SniHandle CreateConnectionHandle(
string fullServerName,
TimeoutTimer timeout,
out byte[] instanceName,
- ref string[] spns,
+ out ResolvedServerSpn resolvedSpn,
string serverSPN,
bool flushCache,
bool async,
@@ -64,6 +64,7 @@ internal static SniHandle CreateConnectionHandle(
string serverCertificateFilename)
{
instanceName = new byte[1];
+ resolvedSpn = default;
bool errorWithLocalDBProcessing;
string localDBDataSource = GetLocalDBDataSource(fullServerName, out errorWithLocalDBProcessing);
@@ -102,7 +103,7 @@ internal static SniHandle CreateConnectionHandle(
{
try
{
- spns = GetSqlServerSPNs(details, serverSPN);
+ resolvedSpn = GetSqlServerSPNs(details, serverSPN);
}
catch (Exception e)
{
@@ -114,12 +115,12 @@ internal static SniHandle CreateConnectionHandle(
return sniHandle;
}
- private static string[] GetSqlServerSPNs(DataSource dataSource, string serverSPN)
+ private static ResolvedServerSpn GetSqlServerSPNs(DataSource dataSource, string serverSPN)
{
Debug.Assert(!string.IsNullOrWhiteSpace(dataSource.ServerName));
if (!string.IsNullOrWhiteSpace(serverSPN))
{
- return new[] { serverSPN };
+ return new(serverSPN);
}
string hostName = dataSource.ServerName;
@@ -137,7 +138,7 @@ private static string[] GetSqlServerSPNs(DataSource dataSource, string serverSPN
return GetSqlServerSPNs(hostName, postfix, dataSource.ResolvedProtocol);
}
- private static string[] GetSqlServerSPNs(string hostNameOrAddress, string portOrInstanceName, DataSource.Protocol protocol)
+ private static ResolvedServerSpn GetSqlServerSPNs(string hostNameOrAddress, string portOrInstanceName, DataSource.Protocol protocol)
{
Debug.Assert(!string.IsNullOrWhiteSpace(hostNameOrAddress));
IPHostEntry hostEntry = null;
@@ -168,12 +169,12 @@ private static string[] GetSqlServerSPNs(string hostNameOrAddress, string portOr
string serverSpnWithDefaultPort = serverSpn + $":{DefaultSqlServerPort}";
// Set both SPNs with and without Port as Port is optional for default instance
SqlClientEventSource.Log.TryAdvancedTraceEvent("SNIProxy.GetSqlServerSPN | Info | ServerSPNs {0} and {1}", serverSpn, serverSpnWithDefaultPort);
- return new[] { serverSpn, serverSpnWithDefaultPort };
+ return new(serverSpn, serverSpnWithDefaultPort);
}
// else Named Pipes do not need to valid port
SqlClientEventSource.Log.TryAdvancedTraceEvent("SNIProxy.GetSqlServerSPN | Info | ServerSPN {0}", serverSpn);
- return new[] { serverSpn };
+ return new(serverSpn);
}
///
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs
index 656060beeb..8435a3d3d5 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs
@@ -375,6 +375,8 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout,
IEnumerable ipAddresses = GetHostAddressesSortedByPreference(serverName, ipPreference);
+ SocketException lastSocketException = null;
+
foreach (IPAddress ipAddress in ipAddresses)
{
bool isSocketSelected = false;
@@ -426,7 +428,9 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout,
{
if (timeout.IsExpired)
{
- return null;
+ throw new Win32Exception(
+ TdsEnums.SNI_WAIT_TIMEOUT,
+ StringsHelper.GetString(Strings.SQL_ConnectTimeout));
}
int socketSelectTimeout =
@@ -442,10 +446,24 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout,
Socket.Select(checkReadLst, checkWriteLst, checkErrorLst, socketSelectTimeout);
// nothing selected means timeout
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO,
+ "Socket.Select results: checkReadLst.Count: {0}, checkWriteLst.Count: {1}, checkErrorLst.Count: {2}",
+ checkReadLst.Count, checkWriteLst.Count, checkErrorLst.Count);
} while (checkReadLst.Count == 0 && checkWriteLst.Count == 0 && checkErrorLst.Count == 0);
// workaround: false positive socket.Connected on linux: https://github.com/dotnet/runtime/issues/55538
isConnected = socket.Connected && checkErrorLst.Count == 0;
+ if (!isConnected)
+ {
+ // Retrieve the socket error code
+ int socketErrorCode = (int)socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Error);
+ SocketError socketError = (SocketError)socketErrorCode;
+
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR,
+ "Socket connection failed. SocketError: {0} ({1})", socketError, socketErrorCode);
+
+ lastSocketException = new SocketException(socketErrorCode);
+ }
}
if (isConnected)
@@ -463,6 +481,8 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout,
}
pendingDNSInfo = new SQLDNSInfo(cachedFQDN, iPv4String, iPv6String, port.ToString());
isSocketSelected = true;
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO,
+ "Connected to socket: {0}", socket.RemoteEndPoint);
return socket;
}
}
@@ -471,6 +491,7 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout,
SqlClientEventSource.Log.TryAdvancedTraceEvent(
"{0}.{1}{2}THIS EXCEPTION IS BEING SWALLOWED: {3}",
nameof(SniTcpHandle), nameof(Connect), EventType.ERR, e);
+ lastSocketException = e;
}
finally
{
@@ -479,6 +500,14 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout,
}
}
+ if (lastSocketException != null)
+ {
+ SqlClientEventSource.Log.TryAdvancedTraceEvent(
+ "{0}.{1}{2}Last Socket Exception: {3}",
+ nameof(SniTcpHandle), nameof(Connect), EventType.ERR, lastSocketException);
+ throw lastSocketException;
+ }
+
return null;
}
}
@@ -574,6 +603,20 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim
Socket.Select(checkReadLst, checkWriteLst, checkErrorLst, socketSelectTimeout);
// nothing selected means select timed out
} while (checkReadLst.Count == 0 && checkWriteLst.Count == 0 && checkErrorLst.Count == 0 && !timeout.IsExpired);
+ foreach (Socket socket in checkErrorLst)
+ {
+ // Retrieve the socket error code
+ int socketErrorCode = (int)socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Error);
+ SocketError socketError = (SocketError)socketErrorCode;
+
+ // Log any failed sockets
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO,
+ "Socket connection failed for {0}. SocketError: {1} ({2})",
+ sockets[socket], socketError, socketErrorCode);
+
+ lastError = new SocketException(socketErrorCode);
+ }
+
}
catch (SocketException e)
{
@@ -588,6 +631,7 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim
{
SqlClientEventSource.Log.TryAdvancedTraceEvent(
"{0}.{1}{2}ParallelConnect timeout expired.", nameof(SniTcpHandle), nameof(ParallelConnect), EventType.INFO);
+ // We will throw below after cleanup
break;
}
@@ -654,9 +698,21 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim
if (connectedSocket == null)
{
+ if (timeout.IsExpired)
+ {
+ throw new Win32Exception(
+ TdsEnums.SNI_WAIT_TIMEOUT,
+ StringsHelper.GetString(Strings.SQL_ConnectTimeout));
+ }
+
SqlClientEventSource.Log.TryAdvancedTraceEvent(
- "{0}.{1}{2}No socket connections succeeded. Last error: {3}",
+ "{0}.{1}{2} No socket connections succeeded. Last error: {3}",
nameof(SniTcpHandle), nameof(ParallelConnect), EventType.ERR, lastError);
+
+ if (lastError != null)
+ {
+ throw lastError;
+ }
}
return connectedSocket;
@@ -861,7 +917,7 @@ public override uint Receive(out SniPacket packet, int timeoutInMilliseconds)
packet = null;
var e = new Win32Exception();
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, Win32 exception occurred: {1}", args0: _connectionId, args1: e?.Message);
- return ReportErrorAndReleasePacket(errorPacket, (uint)e.NativeErrorCode, 0, e.Message);
+ return ReportErrorAndReleasePacket(errorPacket, e.NativeErrorCode, 0, e.Message);
}
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, Data read from stream synchronously", args0: _connectionId);
@@ -897,9 +953,30 @@ public override uint Receive(out SniPacket packet, int timeoutInMilliseconds)
}
finally
{
- // Reset the socket timeout to Timeout.Infinite after the receive operation is done
- // to avoid blocking the thread in case of a timeout error.
- _socket.ReceiveTimeout = Timeout.Infinite;
+ const int resetTimeout = Timeout.Infinite;
+
+ try
+ {
+ // Reset the socket timeout to Timeout.Infinite after
+ // the receive operation is done to avoid blocking the
+ // thread in case of a timeout error.
+ _socket.ReceiveTimeout = resetTimeout;
+
+ }
+ catch (SocketException ex)
+ {
+ // We sometimes see setting the ReceiveTimeout fail
+ // on macOS. There's isn't much we can do about it
+ // though, so just log and move on.
+ SqlClientEventSource.Log.TrySNITraceEvent(
+ nameof(SniTcpHandle),
+ EventType.ERR,
+ "Connection Id {0}, Failed to reset socket " +
+ "receive timeout to {1}: {2}",
+ _connectionId,
+ resetTimeout,
+ ex.Message);
+ }
}
}
}
@@ -992,13 +1069,13 @@ public override uint CheckConnection()
return TdsEnums.SNI_SUCCESS;
}
- private uint ReportTcpSNIError(Exception sniException, uint nativeErrorCode = 0)
+ private uint ReportTcpSNIError(Exception sniException, int nativeErrorCode = 0)
{
_status = TdsEnums.SNI_ERROR;
return SniCommon.ReportSNIError(SniProviders.TCP_PROV, SniCommon.InternalExceptionError, sniException, nativeErrorCode);
}
- private uint ReportTcpSNIError(uint nativeError, uint sniError, string errorMessage)
+ private uint ReportTcpSNIError(int nativeError, uint sniError, string errorMessage)
{
_status = TdsEnums.SNI_ERROR;
return SniCommon.ReportSNIError(SniProviders.TCP_PROV, nativeError, sniError, errorMessage);
@@ -1013,7 +1090,7 @@ private uint ReportErrorAndReleasePacket(SniPacket packet, Exception sniExceptio
return ReportTcpSNIError(sniException);
}
- private uint ReportErrorAndReleasePacket(SniPacket packet, uint nativeError, uint sniError, string errorMessage)
+ private uint ReportErrorAndReleasePacket(SniPacket packet, int nativeError, uint sniError, string errorMessage)
{
if (packet != null)
{
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Packet.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Packet.cs
deleted file mode 100644
index b81270bf08..0000000000
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Packet.cs
+++ /dev/null
@@ -1,189 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
-
-namespace Microsoft.Data.SqlClient
-{
- ///
- /// Contains a buffer for a partial or full packet and methods to get information about the status of
- /// the packet that the buffer represents.
- /// This class is used to contain partial packet data and helps ensure that the packet data is completely
- /// received before a full packet is made available to the rest of the library
- ///
- internal sealed partial class Packet
- {
- public const int UnknownDataLength = -1;
-
- private int _dataLength;
- private int _totalLength;
- private byte[] _buffer;
-
- public Packet()
- {
- _dataLength = UnknownDataLength;
- }
-
- ///
- /// If the packet data has enough bytes available to determine the amount of data that should be present
- /// in the packet then this property will be set to the count of data bytes in the packet,
- /// otherwise this will be -1
- ///
- public int DataLength
- {
- get
- {
- CheckDisposed();
- return _dataLength;
- }
- set
- {
- CheckDisposed();
- _dataLength = value;
- }
- }
-
- ///
- /// A byte array containing bytes of data
- ///
- public byte[] Buffer
- {
- get
- {
- CheckDisposed();
- return _buffer;
- }
- set
- {
- CheckDisposed();
- _buffer = value;
- }
- }
-
- ///
- /// The total count of bytes currently in the array including the tds header bytes
- ///
- public int CurrentLength
- {
- get
- {
- CheckDisposed();
- return _totalLength;
- }
- set
- {
- CheckDisposed();
- _totalLength = value;
- }
- }
-
- ///
- /// If the packet data has enough bytes available to determine the length amount of data that should be present
- /// in the packet then this property will return the count of data bytes that are expected to be in the packet.
- /// If there are not enough bytes to determine the data byte count then this property will throw an exception.
- ///
- /// Call to check if there will be a value before using this property.
- ///
- public int RequiredLength
- {
- get
- {
- CheckDisposed();
- if (!HasDataLength)
- {
- throw new InvalidOperationException($"cannot get {nameof(RequiredLength)} when {nameof(HasDataLength)} is false");
- }
- return TdsEnums.HEADER_LEN + _dataLength;
- }
- }
-
- ///
- /// returns a boolean value indicating if there are enough total bytes available in the to read the tds header
- ///
- public bool HasHeader => _totalLength >= TdsEnums.HEADER_LEN;
-
- ///
- /// returns a boolean value indicating if the value has been set.
- ///
- public bool HasDataLength => _dataLength >= 0;
-
- ///
- /// returns a boolean value indicating whether the contains enough
- /// data for a valid tds header, has a set and that the
- /// is greater than or equal to the + tds header length.
- ///
- public bool ContainsCompletePacket => _dataLength != UnknownDataLength && (TdsEnums.HEADER_LEN + _dataLength) <= _totalLength;
-
- ///
- /// returns a containing the first 8 bytes of the array which will
- /// contain the TDS header bytes. This can be passed to static functions on to extract information from the
- /// tds packet header.
- /// Call before using this function.
- ///
- ///
- public ReadOnlySpan GetHeaderSpan() => _buffer.AsSpan(0, TdsEnums.HEADER_LEN);
-
- [Conditional("DEBUG")]
- internal void CheckDisposed() => CheckDisposedImpl();
-
- [Conditional("DEBUG")]
- internal void SetCreatedBy(int creator) => SetCreatedByImpl(creator);
-
- partial void SetCreatedByImpl(int creator);
-
- partial void CheckDisposedImpl();
-
- public static void ThrowDisposed()
- {
- throw new ObjectDisposedException(nameof(Packet));
- }
-
- internal static byte GetStatusFromHeader(ReadOnlySpan header) => header[1];
-
- internal static int GetDataLengthFromHeader(ReadOnlySpan header)
- {
- return (header[TdsEnums.HEADER_LEN_FIELD_OFFSET] << 8 | header[TdsEnums.HEADER_LEN_FIELD_OFFSET + 1]) - TdsEnums.HEADER_LEN;
- }
- internal static int GetSpidFromHeader(ReadOnlySpan header)
- {
- return (header[TdsEnums.SPID_OFFSET] << 8 | header[TdsEnums.SPID_OFFSET + 1]);
- }
- internal static int GetIDFromHeader(ReadOnlySpan header)
- {
- return header[TdsEnums.HEADER_LEN_FIELD_OFFSET + 4];
- }
-
- internal static int GetDataLengthFromHeader(Packet packet) => GetDataLengthFromHeader(packet.GetHeaderSpan());
-
- internal static bool GetIsEOMFromHeader(ReadOnlySpan header) => GetStatusFromHeader(header) == 1;
- }
-
-#if DEBUG
- internal sealed partial class Packet
- {
- private int _createdBy;
- private bool _disposed;
-
- public int CreatedBy => _createdBy;
-
- [Conditional("DEBUG")]
- public void Dispose()
- {
- _disposed = true;
- }
-
- partial void SetCreatedByImpl(int creator) => _createdBy = creator;
-
- partial void CheckDisposedImpl()
- {
- if (_disposed)
- {
- ThrowDisposed();
- }
- }
- }
-#endif
-}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSspiContextProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSspiContextProvider.cs
index 5dc52010b3..a74651cf2d 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSspiContextProvider.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSspiContextProvider.cs
@@ -2,26 +2,25 @@
using System;
using System.Buffers;
+using System.Diagnostics;
using System.Net.Security;
#nullable enable
namespace Microsoft.Data.SqlClient
{
- internal sealed class NegotiateSspiContextProvider : SspiContextProvider
+ internal sealed class NegotiateSspiContextProvider : SspiContextProvider, IDisposable
{
- private NegotiateAuthentication? _negotiateAuth = null;
+ private NegotiateAuthentication? _negotiateAuth;
protected override bool GenerateSspiClientContext(ReadOnlySpan incomingBlob, IBufferWriter outgoingBlobWriter, SspiAuthenticationParameters authParams)
{
- NegotiateAuthenticationStatusCode statusCode = NegotiateAuthenticationStatusCode.UnknownCredentials;
-
- _negotiateAuth ??= new(new NegotiateAuthenticationClientOptions { Package = "Negotiate", TargetName = authParams.Resource });
- var sendBuff = _negotiateAuth.GetOutgoingBlob(incomingBlob, out statusCode)!;
+ var negotiateAuth = GetNegotiateAuthenticationForParams(authParams);
+ var sendBuff = negotiateAuth.GetOutgoingBlob(incomingBlob, out var statusCode)!;
// Log session id, status code and the actual SPN used in the negotiation
SqlClientEventSource.Log.TryTraceEvent("{0}.{1} | Info | Session Id {2}, StatusCode={3}, SPN={4}", nameof(NegotiateSspiContextProvider),
- nameof(GenerateSspiClientContext), _physicalStateObj.SessionId, statusCode, _negotiateAuth.TargetName);
+ nameof(GenerateSspiClientContext), _physicalStateObj.SessionId, statusCode, negotiateAuth.TargetName);
if (statusCode == NegotiateAuthenticationStatusCode.Completed || statusCode == NegotiateAuthenticationStatusCode.ContinueNeeded)
{
@@ -31,6 +30,27 @@ protected override bool GenerateSspiClientContext(ReadOnlySpan incomingBlo
return false;
}
+
+ public void Dispose()
+ {
+ _negotiateAuth?.Dispose();
+ }
+
+ private NegotiateAuthentication GetNegotiateAuthenticationForParams(SspiAuthenticationParameters authParams)
+ {
+ if (_negotiateAuth is { })
+ {
+ if (string.Equals(_negotiateAuth.TargetName, authParams.Resource, StringComparison.Ordinal))
+ {
+ return _negotiateAuth;
+ }
+
+ // Dispose of it since we're not going to use it now
+ _negotiateAuth.Dispose();
+ }
+
+ return _negotiateAuth = new(new NegotiateAuthenticationClientOptions { Package = "Negotiate", TargetName = authParams.Resource });
+ }
}
}
#endif
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiContextProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiContextProvider.cs
index ff83422f10..f45ccee4fd 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiContextProvider.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiContextProvider.cs
@@ -10,15 +10,48 @@ internal abstract class SspiContextProvider
{
private TdsParser _parser = null!;
private ServerInfo _serverInfo = null!;
+
+ private SspiAuthenticationParameters? _primaryAuthParams;
+ private SspiAuthenticationParameters? _secondaryAuthParams;
+
private protected TdsParserStateObject _physicalStateObj = null!;
- internal void Initialize(ServerInfo serverInfo, TdsParserStateObject physicalStateObj, TdsParser parser)
+#if NET
+ ///
+ /// for details as to what and means and why there are two.
+ ///
+#endif
+ internal void Initialize(
+ ServerInfo serverInfo,
+ TdsParserStateObject physicalStateObj,
+ TdsParser parser,
+ string primaryServerSpn,
+ string? secondaryServerSpn = null
+ )
{
_parser = parser;
_physicalStateObj = physicalStateObj;
_serverInfo = serverInfo;
+ var options = parser.Connection.ConnectionOptions;
+
+ SqlClientEventSource.Log.StateDumpEvent(" Initializing provider {0} with SPN={1} and alternate={2}", GetType().FullName, primaryServerSpn, secondaryServerSpn);
+
+ _primaryAuthParams = CreateAuthParams(options, primaryServerSpn);
+
+ if (secondaryServerSpn is { })
+ {
+ _secondaryAuthParams = CreateAuthParams(options, secondaryServerSpn);
+ }
+
Initialize();
+
+ static SspiAuthenticationParameters CreateAuthParams(SqlConnectionString connString, string serverSpn) => new(connString.DataSource, serverSpn)
+ {
+ DatabaseName = connString.InitialCatalog,
+ UserId = connString.UserID,
+ Password = connString.Password,
+ };
}
private protected virtual void Initialize()
@@ -27,46 +60,41 @@ private protected virtual void Initialize()
protected abstract bool GenerateSspiClientContext(ReadOnlySpan incomingBlob, IBufferWriter outgoingBlobWriter, SspiAuthenticationParameters authParams);
- internal void SSPIData(ReadOnlySpan receivedBuff, IBufferWriter outgoingBlobWriter, string serverSpn)
+ internal void WriteSSPIContext(ReadOnlySpan receivedBuff, IBufferWriter outgoingBlobWriter)
{
using var _ = TrySNIEventScope.Create(nameof(SspiContextProvider));
- if (!RunGenerateSspiClientContext(receivedBuff, outgoingBlobWriter, serverSpn))
+ if (_primaryAuthParams is { })
{
- // If we've hit here, the SSPI context provider implementation failed to generate the SSPI context.
- SSPIError(SQLMessage.SSPIGenerateError(), TdsEnums.GEN_CLIENT_CONTEXT);
- }
- }
+ if (RunGenerateSspiClientContext(receivedBuff, outgoingBlobWriter, _primaryAuthParams))
+ {
+ return;
+ }
- internal void SSPIData(ReadOnlySpan receivedBuff, IBufferWriter outgoingBlobWriter, ReadOnlySpan serverSpns)
- {
- using var _ = TrySNIEventScope.Create(nameof(SspiContextProvider));
+ // remove _primaryAuth from future attempts as it failed
+ _primaryAuthParams = null;
+ }
- foreach (var serverSpn in serverSpns)
+ if (_secondaryAuthParams is { })
{
- if (RunGenerateSspiClientContext(receivedBuff, outgoingBlobWriter, serverSpn))
+ if (RunGenerateSspiClientContext(receivedBuff, outgoingBlobWriter, _secondaryAuthParams))
{
return;
}
+
+ // remove _secondaryAuthParams from future attempts as it failed
+ _secondaryAuthParams = null;
}
// If we've hit here, the SSPI context provider implementation failed to generate the SSPI context.
SSPIError(SQLMessage.SSPIGenerateError(), TdsEnums.GEN_CLIENT_CONTEXT);
}
- private bool RunGenerateSspiClientContext(ReadOnlySpan incomingBlob, IBufferWriter outgoingBlobWriter, string serverSpn)
+ private bool RunGenerateSspiClientContext(ReadOnlySpan incomingBlob, IBufferWriter outgoingBlobWriter, SspiAuthenticationParameters authParams)
{
- var options = _parser.Connection.ConnectionOptions;
- var authParams = new SspiAuthenticationParameters(options.DataSource, serverSpn)
- {
- DatabaseName = options.InitialCatalog,
- UserId = options.UserID,
- Password = options.Password,
- };
-
try
{
- SqlClientEventSource.Log.TryTraceEvent("{0}.{1} | Info | SPN={1}", GetType().FullName, nameof(GenerateSspiClientContext), serverSpn);
+ SqlClientEventSource.Log.TryTraceEvent("{0}.{1} | Info | SPN={1}", GetType().FullName, nameof(GenerateSspiClientContext), authParams.Resource);
return GenerateSspiClientContext(incomingBlob, outgoingBlobWriter, authParams);
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs
index e29746dc6c..1fe587720c 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs
@@ -186,7 +186,7 @@ public bool SetProvider(SqlAuthenticationMethod authenticationMethod, SqlAuthent
if (candidateMethod == authenticationMethod)
{
_sqlAuthLogger.LogError(nameof(SqlAuthenticationProviderManager), methodName, $"Failed to add provider {GetProviderType(provider)} because a user-defined provider with type {GetProviderType(_providers[authenticationMethod])} already existed for authentication {authenticationMethod}.");
- break;
+ return false; // return here to avoid replacing user-defined provider
}
}
}
@@ -206,9 +206,18 @@ public bool SetProvider(SqlAuthenticationMethod authenticationMethod, SqlAuthent
return true;
}
+ ///
+ /// Fetches provided configuration section from app.config file.
+ /// Does not support reading from appsettings.json yet.
+ ///
+ ///
+ ///
+ ///
private static T FetchConfigurationSection(string name)
{
Type t = typeof(T);
+
+ // TODO: Support reading configuration from appsettings.json for .NET runtime applications.
object section = ConfigurationManager.GetSection(name);
if (section != null)
{
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs
index 39d2758d62..bf27581b51 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs
@@ -993,7 +993,7 @@ internal SqlVector GetSqlVector() where T : unmanaged
{
if (IsNull)
{
- return new SqlVector(_value._vectorInfo._elementCount);
+ return SqlVector.CreateNull(_value._vectorInfo._elementCount);
}
return new SqlVector(SqlBinary.Value);
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCachedBuffer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCachedBuffer.cs
index 82ef37fb6b..2b656501a5 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCachedBuffer.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCachedBuffer.cs
@@ -37,24 +37,10 @@ private SqlCachedBuffer(List cachedBytes)
///
internal static TdsOperationStatus TryCreate(SqlMetaDataPriv metadata, TdsParser parser, TdsParserStateObject stateObj, out SqlCachedBuffer buffer)
{
- buffer = null;
-
- (bool canContinue, bool isStarting, _) = stateObj.GetSnapshotStatuses();
+ byte[] byteArr;
- List cachedBytes = null;
- if (canContinue)
- {
- cachedBytes = stateObj.TryTakeSnapshotStorage() as List;
- if (isStarting)
- {
- cachedBytes = null;
- }
- }
-
- if (cachedBytes == null)
- {
- cachedBytes = new List();
- }
+ List cachedBytes = new();
+ buffer = null;
// the very first length is already read.
TdsOperationStatus result = parser.TryPlpBytesLeft(stateObj, out ulong plplength);
@@ -73,25 +59,13 @@ internal static TdsOperationStatus TryCreate(SqlMetaDataPriv metadata, TdsParser
}
do
{
- bool returnAfterAdd = false;
int cb = (plplength > (ulong)MaxChunkSize) ? MaxChunkSize : (int)plplength;
- byte[] byteArr = new byte[cb];
- // pass false for the writeDataSizeToSnapshot parameter because we want to only take data
- // from the current packet and not try to do a continue-capable multi packet read
- result = stateObj.TryReadPlpBytes(ref byteArr, 0, cb, out cb, canContinue, writeDataSizeToSnapshot: false, compatibilityMode: false);
+ byteArr = new byte[cb];
+ result = stateObj.TryReadPlpBytes(ref byteArr, 0, cb, out cb);
if (result != TdsOperationStatus.Done)
{
- if (result == TdsOperationStatus.NeedMoreData && canContinue && cb == byteArr.Length)
- {
- // succeeded in getting the data but failed to find the next plp length
- returnAfterAdd = true;
- }
- else
- {
- return result;
- }
+ return result;
}
-
Debug.Assert(cb == byteArr.Length);
if (cachedBytes.Count == 0)
{
@@ -100,13 +74,6 @@ internal static TdsOperationStatus TryCreate(SqlMetaDataPriv metadata, TdsParser
}
cachedBytes.Add(byteArr);
plplength -= (ulong)cb;
-
- if (returnAfterAdd)
- {
- stateObj.SetSnapshotStorage(cachedBytes);
- return result;
- }
-
} while (plplength > 0);
result = parser.TryPlpBytesLeft(stateObj, out plplength);
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs
index 90a69b5670..4988a4f4da 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs
@@ -15,11 +15,13 @@ namespace Microsoft.Data.SqlClient
[EventSource(Name = "Microsoft.Data.SqlClient.EventSource")]
internal partial class SqlClientEventSource : EventSource
{
+ private static bool s_initialMetricsEnabled = false;
+
// Defines the singleton instance for the Resources ETW provider
public static readonly SqlClientEventSource Log = new();
// Provides access to metrics.
- public static readonly SqlClientMetrics Metrics = new SqlClientMetrics(Log);
+ public static readonly SqlClientMetrics Metrics = new SqlClientMetrics(Log, s_initialMetricsEnabled);
private SqlClientEventSource() { }
@@ -33,7 +35,14 @@ protected override void OnEventCommand(EventCommandEventArgs command)
if (command.Command == EventCommand.Enable)
{
- Metrics.EnableEventCounters();
+ if (Metrics == null)
+ {
+ s_initialMetricsEnabled = true;
+ }
+ else
+ {
+ Metrics.EnableEventCounters();
+ }
}
}
#endif
@@ -424,7 +433,7 @@ internal long TryScopeEnterEvent(string className, [System.Runtime.CompilerServi
{
StringBuilder sb = new StringBuilder(className);
sb.Append(".").Append(memberName).Append(" | INFO | SCOPE | Entering Scope {0}");
- return SNIScopeEnter(sb.ToString());
+ return ScopeEnter(sb.ToString());
}
return 0;
}
@@ -893,6 +902,12 @@ internal void StateDumpEvent(string message, T0 args0, T1 args1)
{
StateDump(string.Format(message, args0?.ToString() ?? NullStr, args1?.ToString() ?? NullStr));
}
+
+ [NonEvent]
+ internal void StateDumpEvent(string message, T0 args0, T1 args1, T2 args2)
+ {
+ StateDump(string.Format(message, args0?.ToString() ?? NullStr, args1?.ToString() ?? NullStr, args2?.ToString() ?? NullStr));
+ }
#endregion
#region SNI Trace
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs
index 3816e74e49..b96a946ac7 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs
@@ -67,7 +67,7 @@ internal static class DEFAULT
internal const string FailoverPartnerSPN = DbConnectionStringDefaults.FailoverPartnerSPN;
internal const bool Context_Connection = DbConnectionStringDefaults.ContextConnection;
#if NETFRAMEWORK
- internal static readonly bool TransparentNetworkIPResolution = DbConnectionStringDefaults.TransparentNetworkIPResolution;
+ internal static bool TransparentNetworkIPResolution => DbConnectionStringDefaults.TransparentNetworkIpResolution;
internal const bool Connection_Reset = DbConnectionStringDefaults.ConnectionReset;
internal const string Network_Library = DbConnectionStringDefaults.NetworkLibrary;
#endif // NETFRAMEWORK
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs
index 6c3e5989f4..7d971cda3e 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs
@@ -130,7 +130,7 @@ private enum Keywords
#if NETFRAMEWORK
private bool _connectionReset = DbConnectionStringDefaults.ConnectionReset;
- private bool _transparentNetworkIPResolution = DbConnectionStringDefaults.TransparentNetworkIPResolution;
+ private bool _transparentNetworkIPResolution = DbConnectionStringDefaults.TransparentNetworkIpResolution;
private string _networkLibrary = DbConnectionStringDefaults.NetworkLibrary;
#endif
#endregion //Fields
@@ -549,7 +549,7 @@ private void Reset(Keywords index)
_connectionReset = DbConnectionStringDefaults.ConnectionReset;
break;
case Keywords.TransparentNetworkIPResolution:
- _transparentNetworkIPResolution = DbConnectionStringDefaults.TransparentNetworkIPResolution;
+ _transparentNetworkIPResolution = DbConnectionStringDefaults.TransparentNetworkIpResolution;
break;
case Keywords.NetworkLibrary:
_networkLibrary = DbConnectionStringDefaults.NetworkLibrary;
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs
index d1f3f5c1a5..353dd0346f 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs
@@ -3365,10 +3365,7 @@ private T GetFieldValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met
{
if (typeof(T) == typeof(string) && metaData.metaType.SqlDbType == SqlDbTypeExtensions.Vector)
{
- if (data.IsNull)
- return (T)(object)data.String;
- else
- return (T)(object)data.GetSqlVector().GetString();
+ return (T)(object)data.String;
}
// the requested type is likely to be one that isn't supported so try the cast and
// unless there is a null value conversion then feedback the cast exception with
@@ -4598,12 +4595,7 @@ private TdsOperationStatus TryResetBlobState()
#if DEBUG
else
{
- Debug.Assert(
- (_sharedState._columnDataBytesRemaining == 0 || _sharedState._columnDataBytesRemaining == -1)
- &&
- (_stateObj._longlen == 0 || _stateObj.IsSnapshotContinuing()),
- "Haven't read header yet, but column is partially read?"
- );
+ Debug.Assert((_sharedState._columnDataBytesRemaining == 0 || _sharedState._columnDataBytesRemaining == -1) && _stateObj._longlen == 0, "Haven't read header yet, but column is partially read?");
}
#endif
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlError.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlError.cs
index 6390729bad..fc3a1247f3 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlError.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlError.cs
@@ -33,16 +33,16 @@ public sealed class SqlError
// to find and invoke the functions, changing the signatures will break many
// things elsewhere
- internal SqlError(int infoNumber, byte errorState, byte errorClass, string server, string errorMessage, string procedure, int lineNumber, uint win32ErrorCode, Exception exception = null)
+ internal SqlError(int infoNumber, byte errorState, byte errorClass, string server, string errorMessage, string procedure, int lineNumber, int win32ErrorCode, Exception exception = null)
: this(infoNumber, errorState, errorClass, server, errorMessage, procedure, lineNumber, win32ErrorCode, exception, -1)
{
}
- internal SqlError(int infoNumber, byte errorState, byte errorClass, string server, string errorMessage, string procedure, int lineNumber, uint win32ErrorCode, Exception exception, int batchIndex)
+ internal SqlError(int infoNumber, byte errorState, byte errorClass, string server, string errorMessage, string procedure, int lineNumber, int win32ErrorCode, Exception exception, int batchIndex)
: this(infoNumber, errorState, errorClass, server, errorMessage, procedure, lineNumber, exception, batchIndex)
{
_server = server;
- _win32ErrorCode = (int)win32ErrorCode;
+ _win32ErrorCode = win32ErrorCode;
}
internal SqlError(int infoNumber, byte errorState, byte errorClass, string server, string errorMessage, string procedure, int lineNumber, Exception exception = null)
@@ -103,7 +103,7 @@ public override string ToString()
///
public int LineNumber => _lineNumber;
- internal int Win32ErrorCode => _win32ErrorCode;
+ internal int Win32ErrorCode => _win32ErrorCode;
internal Exception Exception => _exception;
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnection.cs
index 742c8b2865..0ea6c7c621 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnection.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnection.cs
@@ -301,6 +301,7 @@ override protected DbReferenceCollection CreateReferenceCollection()
return new SqlReferenceCollection();
}
+ ///
override protected void Deactivate()
{
TdsParser bestEffortCleanupTarget = null;
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs
index f9b940fec3..fb9cdbb8e0 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs
@@ -773,7 +773,7 @@ private object GetVectorReturnValue()
switch (elementType)
{
case MetaType.SqlVectorElementType.Float32:
- return new SqlVector(elementCount);
+ return SqlVector.CreateNull(elementCount);
default:
throw SQL.VectorTypeNotSupported(elementType.ToString());
}
@@ -857,8 +857,13 @@ public override int Size
{
throw ADP.InvalidSizeValue(value);
}
- PropertyChanging();
- _size = value;
+
+ // We ignore the Size property for Vector types, as it is not applicable.
+ if (_metaType == null || _metaType.SqlDbType != SqlDbTypeExtensions.Vector)
+ {
+ PropertyChanging();
+ _size = value;
+ }
}
}
}
@@ -1970,7 +1975,8 @@ internal void Prepare(SqlCommand cmd)
{
throw ADP.PrepareParameterType(cmd);
}
- else if (!ShouldSerializeSize() && !_metaType.IsFixed)
+ // For vector datatype we do not require size to be specified. It is inferred from the SqlParameter.Value.
+ else if (!ShouldSerializeSize() && !_metaType.IsFixed && _metaType.SqlDbType != SqlDbTypeExtensions.Vector)
{
throw ADP.PrepareParameterSize(cmd);
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlStatistics.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlStatistics.cs
index 1ea104d065..c88cddff24 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlStatistics.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlStatistics.cs
@@ -38,7 +38,7 @@ internal static ValueSqlStatisticsScope TimedScope(SqlStatistics statistics)
// internal values that are not exposed through properties
internal long _closeTimestamp;
internal long _openTimestamp;
- internal long _startExecutionTimestamp;
+ internal long? _startExecutionTimestamp;
internal long _startFetchTimestamp;
internal long _startNetworkServerTimestamp;
@@ -80,7 +80,7 @@ internal bool WaitForDoneAfterRow
internal void ContinueOnNewConnection()
{
- _startExecutionTimestamp = 0;
+ _startExecutionTimestamp = null;
_startFetchTimestamp = 0;
_waitForDoneAfterRow = false;
_waitForReply = false;
@@ -108,7 +108,7 @@ internal IDictionary GetDictionary()
{ "UnpreparedExecs", _unpreparedExecs },
{ "ConnectionTime", ADP.TimerToMilliseconds(_connectionTime) },
- { "ExecutionTime", ADP.TimerToMilliseconds(_executionTime) },
+ { "ExecutionTime", _executionTime },
{ "NetworkServerTime", ADP.TimerToMilliseconds(_networkServerTime) }
};
Debug.Assert(dictionary.Count == Count);
@@ -117,9 +117,9 @@ internal IDictionary GetDictionary()
internal bool RequestExecutionTimer()
{
- if (_startExecutionTimestamp == 0)
+ if (!_startExecutionTimestamp.HasValue)
{
- _startExecutionTimestamp = ADP.TimerCurrent();
+ _startExecutionTimestamp = ADP.FastTimerCurrent();
return true;
}
return false;
@@ -127,7 +127,7 @@ internal bool RequestExecutionTimer()
internal void RequestNetworkServerTimer()
{
- Debug.Assert(_startExecutionTimestamp != 0, "No network time expected outside execution period");
+ Debug.Assert(_startExecutionTimestamp.HasValue, "No network time expected outside execution period");
if (_startNetworkServerTimestamp == 0)
{
_startNetworkServerTimestamp = ADP.TimerCurrent();
@@ -137,10 +137,11 @@ internal void RequestNetworkServerTimer()
internal void ReleaseAndUpdateExecutionTimer()
{
- if (_startExecutionTimestamp > 0)
+ if (_startExecutionTimestamp.HasValue)
{
- _executionTime += (ADP.TimerCurrent() - _startExecutionTimestamp);
- _startExecutionTimestamp = 0;
+ uint elapsed = ADP.CalculateTickCountElapsed(_startExecutionTimestamp.Value, ADP.FastTimerCurrent());
+ _executionTime += elapsed;
+ _startExecutionTimestamp = null;
}
}
@@ -176,7 +177,7 @@ internal void Reset()
_unpreparedExecs = 0;
_waitForDoneAfterRow = false;
_waitForReply = false;
- _startExecutionTimestamp = 0;
+ _startExecutionTimestamp = null;
_startNetworkServerTimestamp = 0;
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs
index fb9ea2997d..961746fc2d 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs
@@ -5,6 +5,7 @@
using System;
using System.Diagnostics;
using System.Text;
+using System.Threading;
using Microsoft.Extensions.Caching.Memory;
namespace Microsoft.Data.SqlClient
@@ -15,8 +16,8 @@ namespace Microsoft.Data.SqlClient
sealed internal class SqlSymmetricKeyCache
{
private readonly MemoryCache _cache;
- private static readonly SqlSymmetricKeyCache _singletonInstance = new SqlSymmetricKeyCache();
-
+ private static readonly SqlSymmetricKeyCache _singletonInstance = new();
+ private static SemaphoreSlim _cacheLock = new(1, 1);
private SqlSymmetricKeyCache()
{
@@ -35,7 +36,7 @@ internal SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnectio
{
string serverName = connection.DataSource;
Debug.Assert(serverName is not null, @"serverName should not be null.");
- StringBuilder cacheLookupKeyBuilder = new StringBuilder(serverName, capacity: serverName.Length + SqlSecurityUtility.GetBase64LengthFromByteLength(keyInfo.encryptedKey.Length) + keyInfo.keyStoreName.Length + 2/*separators*/);
+ StringBuilder cacheLookupKeyBuilder = new(serverName, capacity: serverName.Length + SqlSecurityUtility.GetBase64LengthFromByteLength(keyInfo.encryptedKey.Length) + keyInfo.keyStoreName.Length + 2/*separators*/);
#if DEBUG
int capacity = cacheLookupKeyBuilder.Capacity;
@@ -52,54 +53,66 @@ internal SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnectio
Debug.Assert(cacheLookupKey.Length <= capacity, "We needed to allocate a larger array");
#endif //DEBUG
- // Lookup the key in cache
- SqlClientSymmetricKey encryptionKey;
- if (!(_cache.TryGetValue(cacheLookupKey, out encryptionKey)))
- {
- Debug.Assert(SqlConnection.ColumnEncryptionTrustedMasterKeyPaths is not null, @"SqlConnection.ColumnEncryptionTrustedMasterKeyPaths should not be null");
-
- SqlSecurityUtility.ThrowIfKeyPathIsNotTrustedForServer(serverName, keyInfo.keyPath);
+ // Acquire the lock to ensure thread safety when accessing the cache
+ _cacheLock.Wait();
- // Key Not found, attempt to look up the provider and decrypt CEK
- if (!SqlSecurityUtility.TryGetColumnEncryptionKeyStoreProvider(keyInfo.keyStoreName, out SqlColumnEncryptionKeyStoreProvider provider, connection, command))
+ try
+ {
+ // Lookup the key in cache
+ if (!(_cache.TryGetValue(cacheLookupKey, out SqlClientSymmetricKey encryptionKey)))
{
- throw SQL.UnrecognizedKeyStoreProviderName(keyInfo.keyStoreName,
- SqlConnection.GetColumnEncryptionSystemKeyStoreProvidersNames(),
- SqlSecurityUtility.GetListOfProviderNamesThatWereSearched(connection, command));
- }
+ Debug.Assert(SqlConnection.ColumnEncryptionTrustedMasterKeyPaths is not null, @"SqlConnection.ColumnEncryptionTrustedMasterKeyPaths should not be null");
- // Decrypt the CEK
- // We will simply bubble up the exception from the DecryptColumnEncryptionKey function.
- byte[] plaintextKey;
- try
- {
- // to prevent conflicts between CEK caches, global providers should not use their own CEK caches
- provider.ColumnEncryptionKeyCacheTtl = new TimeSpan(0);
- plaintextKey = provider.DecryptColumnEncryptionKey(keyInfo.keyPath, keyInfo.algorithmName, keyInfo.encryptedKey);
- }
- catch (Exception e)
- {
- // Generate a new exception and throw.
- string keyHex = SqlSecurityUtility.GetBytesAsString(keyInfo.encryptedKey, fLast: true, countOfBytes: 10);
- throw SQL.KeyDecryptionFailed(keyInfo.keyStoreName, keyHex, e);
- }
+ SqlSecurityUtility.ThrowIfKeyPathIsNotTrustedForServer(serverName, keyInfo.keyPath);
- encryptionKey = new SqlClientSymmetricKey(plaintextKey);
+ // Key Not found, attempt to look up the provider and decrypt CEK
+ if (!SqlSecurityUtility.TryGetColumnEncryptionKeyStoreProvider(keyInfo.keyStoreName, out SqlColumnEncryptionKeyStoreProvider provider, connection, command))
+ {
+ throw SQL.UnrecognizedKeyStoreProviderName(keyInfo.keyStoreName,
+ SqlConnection.GetColumnEncryptionSystemKeyStoreProvidersNames(),
+ SqlSecurityUtility.GetListOfProviderNamesThatWereSearched(connection, command));
+ }
+
+ // Decrypt the CEK
+ // We will simply bubble up the exception from the DecryptColumnEncryptionKey function.
+ byte[] plaintextKey;
+ try
+ {
+ // AKV provider registration supports multi-user scenarios, so it is not safe to cache the CEK in the global provider.
+ // The CEK cache is a global cache, and is shared across all connections.
+ // To prevent conflicts between CEK caches, global providers should not use their own CEK caches
+ provider.ColumnEncryptionKeyCacheTtl = new TimeSpan(0);
+ plaintextKey = provider.DecryptColumnEncryptionKey(keyInfo.keyPath, keyInfo.algorithmName, keyInfo.encryptedKey);
+ }
+ catch (Exception e)
+ {
+ // Generate a new exception and throw.
+ string keyHex = SqlSecurityUtility.GetBytesAsString(keyInfo.encryptedKey, fLast: true, countOfBytes: 10);
+ throw SQL.KeyDecryptionFailed(keyInfo.keyStoreName, keyHex, e);
+ }
- // If the cache TTL is zero, don't even bother inserting to the cache.
- if (SqlConnection.ColumnEncryptionKeyCacheTtl != TimeSpan.Zero)
- {
- // In case multiple threads reach here at the same time, the first one wins.
- // The allocated memory will be reclaimed by Garbage Collector.
- MemoryCacheEntryOptions options = new MemoryCacheEntryOptions
+ encryptionKey = new SqlClientSymmetricKey(plaintextKey);
+
+ // If the cache TTL is zero, don't even bother inserting to the cache.
+ if (SqlConnection.ColumnEncryptionKeyCacheTtl != TimeSpan.Zero)
{
- AbsoluteExpirationRelativeToNow = SqlConnection.ColumnEncryptionKeyCacheTtl
- };
- _cache.Set(cacheLookupKey, encryptionKey, options);
+ // In case multiple threads reach here at the same time, the first one wins.
+ // The allocated memory will be reclaimed by Garbage Collector.
+ MemoryCacheEntryOptions options = new()
+ {
+ AbsoluteExpirationRelativeToNow = SqlConnection.ColumnEncryptionKeyCacheTtl
+ };
+ _cache.Set(cacheLookupKey, encryptionKey, options);
+ }
}
- }
- return encryptionKey;
+ return encryptionKey;
+ }
+ finally
+ {
+ // Release the lock to allow other threads to access the cache
+ _cacheLock.Release();
+ }
}
}
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs
index 630633a6b4..e234fbbef6 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs
@@ -604,7 +604,7 @@ public enum ActiveDirectoryWorkflow : byte
public const uint SNI_UNINITIALIZED = unchecked((uint)-1);
public const uint SNI_SUCCESS = 0; // The operation completed successfully.
public const uint SNI_ERROR = 1; // Error
- public const uint SNI_WAIT_TIMEOUT = 258; // The wait operation timed out.
+ public const int SNI_WAIT_TIMEOUT = 258; // The wait operation timed out.
public const uint SNI_SUCCESS_IO_PENDING = 997; // Overlapped I/O operation is in progress.
// Windows Sockets Error Codes
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs
index 7e0fd18503..6226f958a5 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -39,7 +39,7 @@ internal void ProcessSSPI(int receivedLength)
try
{
// make call for SSPI data
- _authenticationProvider!.SSPIData(receivedBuff.AsSpan(0, receivedLength), writer, _serverSpn);
+ _authenticationProvider!.WriteSSPIContext(receivedBuff.AsSpan(0, receivedLength), writer);
// DO NOT SEND LENGTH - TDS DOC INCORRECT! JUST SEND SSPI DATA!
_physicalStateObj.WriteByteSpan(writer.WrittenSpan);
@@ -179,7 +179,7 @@ internal void TdsLogin(
// byte[] buffer and 0 for the int length.
Debug.Assert(SniContext.Snix_Login == _physicalStateObj.SniContext, $"Unexpected SniContext. Expecting Snix_Login, actual value is '{_physicalStateObj.SniContext}'");
_physicalStateObj.SniContext = SniContext.Snix_LoginSspi;
- _authenticationProvider.SSPIData(ReadOnlySpan.Empty, sspiWriter, _serverSpn);
+ _authenticationProvider.WriteSSPIContext(ReadOnlySpan.Empty, sspiWriter);
_physicalStateObj.SniContext = SniContext.Snix_Login;
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.Multiplexer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.Multiplexer.cs
deleted file mode 100644
index 77bd1c982b..0000000000
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.Multiplexer.cs
+++ /dev/null
@@ -1,551 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Diagnostics;
-
-namespace Microsoft.Data.SqlClient
-{
- partial class TdsParserStateObject
- {
- private Packet _partialPacket;
- internal Packet PartialPacket => _partialPacket;
-
- public void ProcessSniPacket(PacketHandle packet, uint error)
- {
- if (LocalAppContextSwitches.UseCompatibilityProcessSni)
- {
- ProcessSniPacketCompat(packet, error);
- return;
- }
-
- if (error != 0)
- {
- if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken))
- {
- // Do nothing with callback if closed or broken and error not 0 - callback can occur
- // after connection has been closed. PROBLEM IN NETLIB - DESIGN FLAW.
- return;
- }
-
- AddError(_parser.ProcessSNIError(this));
- AssertValidState();
- }
- else
- {
- uint dataSize = 0;
- bool usedPartialPacket = false;
- uint getDataError = 0;
-
- if (PartialPacketContainsCompletePacket())
- {
- Packet partialPacket = _partialPacket;
- SetBuffer(partialPacket.Buffer, 0, partialPacket.CurrentLength);
- ClearPartialPacket();
- getDataError = TdsEnums.SNI_SUCCESS;
- usedPartialPacket = true;
- }
- else
- {
- if (_inBytesRead != 0)
- {
- SetBuffer(new byte[_inBuff.Length], 0, 0);
- }
- getDataError = GetSniPacket(packet, ref dataSize);
- }
-
- if (getDataError == TdsEnums.SNI_SUCCESS)
- {
- if (_inBuff.Length < dataSize)
- {
- Debug.Assert(true, "Unexpected dataSize on Read");
- throw SQL.InvalidInternalPacketSize(StringsHelper.GetString(Strings.SqlMisc_InvalidArraySizeMessage));
- }
-
- if (!usedPartialPacket)
- {
- _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks;
-
- SetBuffer(_inBuff, 0, (int)dataSize);
- }
-
- bool recurse = false;
- bool appended = false;
- do
- {
- if (recurse && appended)
- {
- SetBuffer(new byte[_inBuff.Length], 0, 0);
- appended = false;
- }
- MultiplexPackets(
- _inBuff, _inBytesUsed, _inBytesRead,
- PartialPacket,
- out int newDataOffset,
- out int newDataLength,
- out Packet remainderPacket,
- out bool consumeInputDirectly,
- out bool consumePartialPacket,
- out bool remainderPacketProduced,
- out recurse
- );
- bool bufferIsPartialCompleted = false;
-
- // if a partial packet was reconstructed it must be handled first
- if (consumePartialPacket)
- {
- if (_snapshot != null)
- {
- _snapshot.AppendPacketData(PartialPacket.Buffer, PartialPacket.CurrentLength);
- SetBuffer(new byte[_inBuff.Length], 0, 0);
- appended = true;
- }
- else
- {
- SetBuffer(PartialPacket.Buffer, 0, PartialPacket.CurrentLength);
-
- }
- bufferIsPartialCompleted = true;
- ClearPartialPacket();
- }
-
- // if the remaining data can be processed directly it must be second
- if (consumeInputDirectly)
- {
- // if some data was taken from the new packet adjust the counters
- if (dataSize != newDataLength || 0 != newDataOffset)
- {
- SetBuffer(_inBuff, newDataOffset, newDataLength);
- }
-
- if (_snapshot != null)
- {
- _snapshot.AppendPacketData(_inBuff, _inBytesRead);
- // if we SetBuffer here to clear the packet buffer we will break the attention handling which relies
- // on the attention containing packet remaining in the active buffer even if we're appending to the
- // snapshot so we will have to use the appended variable to prevent the same buffer being added again
- //// SetBuffer(new byte[_inBuff.Length], 0, 0);
- appended = true;
- }
- else
- {
- SetBuffer(_inBuff, 0, _inBytesRead);
- }
- bufferIsPartialCompleted = true;
- }
- else
- {
- // whatever is in the input buffer should not be directly consumed
- // and is contained in the partial or remainder packets so make sure
- // we don't process it
- if (!bufferIsPartialCompleted)
- {
- SetBuffer(_inBuff, 0, 0);
- }
- }
-
- // if there is a remainder it must be last
- if (remainderPacketProduced)
- {
- SetPartialPacket(remainderPacket);
- if (!bufferIsPartialCompleted)
- {
- // we are keeping the partial packet buffer so replace it with a new one
- // unless we have already set the buffer to the partial packet buffer
- SetBuffer(new byte[_inBuff.Length], 0, 0);
- }
- }
-
- } while (recurse && _snapshot != null);
-
- if (_snapshot != null)
- {
- if (_snapshotStatus != SnapshotStatus.NotActive && appended)
- {
- _snapshot.MoveNext();
- }
- }
-
- SniReadStatisticsAndTracing();
- SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParser.ReadNetworkPacketAsyncCallback | INFO | ADV | State Object Id {0}, Packet read. In Buffer {1}, In Bytes Read: {2}", ObjectID, _inBuff, (ushort)_inBytesRead);
-
- AssertValidState();
- }
- else
- {
- throw SQL.ParsingError(ParsingErrorState.ProcessSniPacketFailed);
- }
- }
- }
-
- private void SetPartialPacket(Packet packet)
- {
- if (_partialPacket != null && packet != null)
- {
- throw new InvalidOperationException("partial packet cannot be non-null when setting to non=null");
- }
- _partialPacket = packet;
- }
-
- private void ClearPartialPacket()
- {
- Packet partialPacket = _partialPacket;
- _partialPacket = null;
-#if DEBUG
- if (partialPacket != null)
- {
- partialPacket.Dispose();
- }
-#endif
- }
-
- // this check is used in two places that must be identical so it is
- // extracted into a method, do not inline this method
- internal bool PartialPacketContainsCompletePacket()
- {
- Packet partialPacket = _partialPacket;
- return partialPacket != null && partialPacket.ContainsCompletePacket;
- }
-
- private static void MultiplexPackets(
- byte[] dataBuffer, int dataOffset, int dataLength,
- Packet partialPacket,
- out int newDataOffset,
- out int newDataLength,
- out Packet remainderPacket,
- out bool consumeInputDirectly,
- out bool consumePartialPacket,
- out bool createdRemainderPacket,
- out bool recurse
- )
- {
- Debug.Assert(dataBuffer != null);
-
- ReadOnlySpan data = dataBuffer.AsSpan(dataOffset, dataLength);
- remainderPacket = null;
- consumeInputDirectly = false;
- consumePartialPacket = false;
- createdRemainderPacket = false;
- recurse = false;
-
- newDataLength = dataLength;
- newDataOffset = dataOffset;
-
- int bytesConsumed = 0;
-
- if (partialPacket != null)
- {
- if (!partialPacket.HasDataLength)
- {
- // we need to get enough bytes to read the packet header
- int headerBytesNeeded = Math.Max(0, TdsEnums.HEADER_LEN - partialPacket.CurrentLength);
- if (headerBytesNeeded > 0)
- {
- int headerBytesAvailable = Math.Min(data.Length, headerBytesNeeded);
-
- Span headerTarget = partialPacket.Buffer.AsSpan(partialPacket.CurrentLength, headerBytesAvailable);
- ReadOnlySpan headerSource = data.Slice(0, headerBytesAvailable);
- headerSource.CopyTo(headerTarget);
-
- partialPacket.CurrentLength = partialPacket.CurrentLength + headerBytesAvailable;
- bytesConsumed += headerBytesAvailable;
- data = data.Slice(headerBytesAvailable);
- }
- if (partialPacket.HasHeader)
- {
- partialPacket.DataLength = Packet.GetDataLengthFromHeader(partialPacket);
- }
- }
-
- if (partialPacket.HasDataLength)
- {
- if (partialPacket.CurrentLength < partialPacket.RequiredLength)
- {
- // the packet length is known so take as much data as possible from the incoming
- // data to try and complete the packet
-
- int payloadBytesNeeded = partialPacket.DataLength - (partialPacket.CurrentLength - TdsEnums.HEADER_LEN);
- int payloadBytesAvailable = Math.Min(data.Length, payloadBytesNeeded);
-
- ReadOnlySpan payloadSource = data.Slice(0, payloadBytesAvailable);
- Span payloadTarget = partialPacket.Buffer.AsSpan(partialPacket.CurrentLength, payloadBytesAvailable);
- payloadSource.CopyTo(payloadTarget);
-
- partialPacket.CurrentLength = partialPacket.CurrentLength + payloadBytesAvailable;
- bytesConsumed += payloadBytesAvailable;
- data = data.Slice(payloadBytesAvailable);
- }
- else if (partialPacket.CurrentLength > partialPacket.RequiredLength)
- {
- // the partial packet contains a complete packet of data and also contains
- // data from a following packet
-
- // the TDS spec requires that all packets be of the defined packet size apart from
- // the last packet of a response. This means that is should not possible to have more than
- // 2 packet fragments in a single buffer like this:
- // - first packet caused the partial
- // - second packet is the one we have just unpacked
- // - third packet is the extra data we have found
- // however, due to the timing of cancellation it is possible that a response token stream
- // has ended before an attention message response is sent leaving us with a short final
- // packet and an additional short cancel packet following it
-
- // this should only happen when the caller is trying to consume the partial packet
- // and does not have new input data
-
- int remainderLength = partialPacket.CurrentLength - partialPacket.RequiredLength;
-
- partialPacket.CurrentLength = partialPacket.RequiredLength;
-
- remainderPacket = new Packet
- {
- Buffer = new byte[dataBuffer.Length],
- CurrentLength = remainderLength,
- };
- remainderPacket.SetCreatedBy(1);
-
- ReadOnlySpan remainderSource = partialPacket.Buffer.AsSpan(TdsEnums.HEADER_LEN + partialPacket.DataLength, remainderLength);
- Span remainderTarget = remainderPacket.Buffer.AsSpan(0, remainderLength);
- remainderSource.CopyTo(remainderTarget);
-
- createdRemainderPacket = true;
-
- recurse = SetupRemainderPacket(remainderPacket);
- }
-
- if (partialPacket.CurrentLength == partialPacket.RequiredLength)
- {
- // partial packet has been completed
- consumePartialPacket = true;
- }
- }
-
- if (bytesConsumed > 0)
- {
- if (data.Length > 0)
- {
- // some data has been taken from the buffer and put into the partial
- // packet buffer. We have data left so move the data we have to the
- // start of the buffer so we can pass the buffer back as zero based
- // to the caller avoiding offset calculations in the rest of this method
- Buffer.BlockCopy(
- dataBuffer, dataOffset + bytesConsumed, // from
- dataBuffer, dataOffset, // to
- dataLength - bytesConsumed // for
- );
-#if DEBUG
- // for debugging purposes fill the removed data area with an easily
- // recognisable pattern so we can see if it is misused
- Span removed = dataBuffer.AsSpan(dataOffset + (dataLength - bytesConsumed), bytesConsumed);
- removed.Fill(0xFF);
-#endif
-
- // then recreate the data span so that we're looking at the data
- // that has been moved
- data = dataBuffer.AsSpan(dataOffset, dataLength - bytesConsumed);
- }
-
- newDataLength = dataLength - bytesConsumed;
- }
- }
-
- // partial packet handling should not make decisions about consuming the input buffer
- Debug.Assert(!consumeInputDirectly);
- // partial packet handling may only create a remainder packet when it is trying to consume the partial packet and has no incoming data
- Debug.Assert(!createdRemainderPacket || data.Length == 0);
-
- if (data.Length > 0)
- {
- if (data.Length >= TdsEnums.HEADER_LEN)
- {
- // we have enough bytes to read the packet header and see how
- // much data we are expecting it to contain
- int packetDataLength = Packet.GetDataLengthFromHeader(data);
-
- if (data.Length == TdsEnums.HEADER_LEN + packetDataLength)
- {
- if (!consumePartialPacket)
- {
- // we can tell the caller that they should directly consume the data in
- // the input buffer, this is the happy path
- consumeInputDirectly = true;
- }
- else
- {
- // we took some data from the input to reconstruct the partial packet
- // so we can't tell the caller to directly consume the packet in the
- // input buffer, we need to construct a new remainder packet and then
- // tell them to consume it
- remainderPacket = new Packet
- {
- Buffer = dataBuffer,
- CurrentLength = data.Length
- };
- remainderPacket.SetCreatedBy(2);
- createdRemainderPacket = true;
- recurse = SetupRemainderPacket(remainderPacket);
- }
- }
- else if (data.Length < TdsEnums.HEADER_LEN + packetDataLength)
- {
- // an incomplete packet so create a remainder packet to pass back
- remainderPacket = new Packet
- {
- Buffer = dataBuffer,
- DataLength = packetDataLength,
- CurrentLength = data.Length
- };
- remainderPacket.SetCreatedBy(3);
- createdRemainderPacket = true;
- recurse = SetupRemainderPacket(remainderPacket);
- }
- else // implied: current length > required length
- {
- // more data than required so need to split it out, but we can't do that
- // here so we need to tell the caller to take the remainder packet and then
- // call this function again
- if (consumePartialPacket)
- {
- // we are already telling the caller to consume the partial packet so we
- // can't tell them it to also consume the data in the buffer directly
- // so create a remainder packet and pass it back.
- remainderPacket = new Packet
- {
- Buffer = new byte[dataBuffer.Length],
- CurrentLength = data.Length
- };
- remainderPacket.SetCreatedBy(4);
- ReadOnlySpan remainderSource = data;
- Span remainderTarget = remainderPacket.Buffer.AsSpan(0, remainderPacket.CurrentLength);
- remainderSource.CopyTo(remainderTarget);
-
- createdRemainderPacket = true;
-
- recurse = SetupRemainderPacket(remainderPacket);
- }
- else
- {
- newDataLength = TdsEnums.HEADER_LEN + packetDataLength;
- int remainderLength = data.Length - (TdsEnums.HEADER_LEN + packetDataLength);
- remainderPacket = new Packet
- {
- Buffer = new byte[dataBuffer.Length],
- CurrentLength = remainderLength
- };
- remainderPacket.SetCreatedBy(5);
-
- ReadOnlySpan remainderSource = data.Slice(TdsEnums.HEADER_LEN + packetDataLength);
- Span remainderTarget = remainderPacket.Buffer.AsSpan(0, remainderLength);
- remainderSource.CopyTo(remainderTarget);
-#if DEBUG
- // for debugging purposes fill the removed data area with an easily
- // recognisable pattern so we can see if it is misused
- Span removed = dataBuffer.AsSpan(TdsEnums.HEADER_LEN + packetDataLength, remainderLength);
- removed.Fill(0xFF);
-#endif
- createdRemainderPacket = true;
- recurse = SetupRemainderPacket(remainderPacket);
-
- consumeInputDirectly = true;
- }
- }
- }
- else
- {
- // either:
- // 1) we took some data from the input to reconstruct the partial packet
- // 2) there was less than a single packet header of data received
- // in both cases we can't tell the caller to directly consume the packet
- // in the input buffer, we need to construct a new remainder packet with
- // the incomplete data and let the caller deal with it
- remainderPacket = new Packet
- {
- Buffer = dataBuffer,
- CurrentLength = data.Length
- };
- remainderPacket.SetCreatedBy(6);
- createdRemainderPacket = true;
- recurse = SetupRemainderPacket(remainderPacket);
- }
- }
-
- if (consumePartialPacket && consumeInputDirectly)
- {
- throw new InvalidOperationException($"MultiplexPackets cannot return both {nameof(consumePartialPacket)} and {nameof(consumeInputDirectly)}");
- }
- }
-
- private static bool SetupRemainderPacket(Packet packet)
- {
- Debug.Assert(packet != null);
- bool containsFullPacket = false;
- if (packet.HasHeader)
- {
- packet.DataLength = Packet.GetDataLengthFromHeader(packet);
- if (packet.HasDataLength && packet.CurrentLength >= packet.RequiredLength)
- {
- containsFullPacket = true;
- }
- }
-
- return containsFullPacket;
- }
-
-
- public void ProcessSniPacketCompat(PacketHandle packet, uint error)
- {
- if (error != 0)
- {
- if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken))
- {
- // Do nothing with callback if closed or broken and error not 0 - callback can occur
- // after connection has been closed. PROBLEM IN NETLIB - DESIGN FLAW.
- return;
- }
-
- AddError(_parser.ProcessSNIError(this));
- AssertValidState();
- }
- else
- {
- uint dataSize = 0;
- uint getDataError = SniPacketGetData(packet, _inBuff, ref dataSize);
-
- if (getDataError == TdsEnums.SNI_SUCCESS)
- {
- if (_inBuff.Length < dataSize)
- {
- Debug.Assert(true, "Unexpected dataSize on Read");
- throw SQL.InvalidInternalPacketSize(StringsHelper.GetString(Strings.SqlMisc_InvalidArraySizeMessage));
- }
-
- _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks;
- _inBytesRead = (int)dataSize;
- _inBytesUsed = 0;
-
- if (_snapshot != null)
- {
- _snapshot.AppendPacketData(_inBuff, _inBytesRead);
- if (_snapshotStatus != SnapshotStatus.NotActive)
- {
- _snapshot.MoveNext();
-#if DEBUG
- _snapshot.AssertCurrent();
-#endif
- }
- }
-
- SniReadStatisticsAndTracing();
- SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParser.ReadNetworkPacketAsyncCallback | INFO | ADV | State Object Id {0}, Packet read. In Buffer: {1}, In Bytes Read: {2}", ObjectID, _inBuff, _inBytesRead);
-
- AssertValidState();
- }
- else
- {
- throw SQL.ParsingError(ParsingErrorState.ProcessSniPacketFailed);
- }
- }
- }
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
index a1b820bb0d..da0e06cc14 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
@@ -7,7 +7,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Security;
-using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -52,6 +51,28 @@ internal enum SnapshottedStateFlags : byte
AttentionReceived = 1 << 5 // NOTE: Received is not volatile as it is only ever accessed\modified by TryRun its callees (i.e. single threaded access)
}
+ internal readonly struct SniErrorDetails
+ {
+ public readonly string ErrorMessage;
+ public readonly int NativeError;
+ public readonly uint SniErrorNumber;
+ public readonly int Provider;
+ public readonly uint LineNumber;
+ public readonly string Function;
+ public readonly Exception Exception;
+
+ internal SniErrorDetails(string errorMessage, int nativeError, uint sniErrorNumber, int provider, uint lineNumber, string function, Exception exception = null)
+ {
+ ErrorMessage = errorMessage;
+ NativeError = nativeError;
+ SniErrorNumber = sniErrorNumber;
+ Provider = provider;
+ LineNumber = lineNumber;
+ Function = function;
+ Exception = exception;
+ }
+ }
+
private sealed class TimeoutState
{
public const int Stopped = 0;
@@ -69,14 +90,6 @@ public TimeoutState(int value)
public int IdentityValue => _value;
}
- private enum SnapshotStatus
- {
- NotActive,
- ReplayStarting,
- ReplayRunning,
- ContinueRunning
- }
-
private const int AttentionTimeoutSeconds = 5;
// Ticks to consider a connection "good" after a successful I/O (10,000 ticks = 1 ms)
@@ -229,7 +242,7 @@ private enum SnapshotStatus
internal TaskCompletionSource
+
+
+ Always
+
@@ -94,6 +92,7 @@
+
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/MultiplexerTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/MultiplexerTests.cs
deleted file mode 100644
index 288586fb17..0000000000
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/MultiplexerTests.cs
+++ /dev/null
@@ -1,724 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Buffers.Binary;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Text.RegularExpressions;
-using Xunit;
-
-namespace Microsoft.Data.SqlClient.Tests
-{
- public class MultiplexerTests
- {
- public static bool IsUsingCompatibilityProcessSni
- {
- get
- {
- if (AppContext.TryGetSwitch(@"Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni", out bool foundValue))
- {
- return foundValue;
- }
- return false;
- }
- }
-
- public static bool IsUsingModernProcessSni => !IsUsingCompatibilityProcessSni;
-
- [ExcludeFromCodeCoverage]
- public static IEnumerable IsAsync()
- {
- yield return new object[] { false };
- yield return new object[] { true };
- }
-
- [ExcludeFromCodeCoverage]
- public static IEnumerable OnlyAsync() { yield return new object[] { true }; }
-
- [ConditionalTheory(nameof(IsUsingModernProcessSni)), MemberData(nameof(IsAsync))]
- public static void PassThroughSinglePacket(bool isAsync)
- {
- int dataSize = 20;
- var a = CreatePacket(dataSize, 0xF);
- List input = new List { a };
- List expected = new List { a };
-
- Assert.Equal(SumPacketLengths(expected), SumPacketLengths(input));
-
- var output = MultiplexPacketList(isAsync, dataSize, input);
-
- ComparePacketLists(dataSize, expected, output);
- }
-
- [ConditionalTheory(nameof(IsUsingModernProcessSni)), MemberData(nameof(IsAsync))]
- public static void PassThroughMultiplePacket(bool isAsync)
- {
- int dataSize = 40;
- List input = CreatePackets(dataSize, 5, 6, 7, 8);
- List expected = input;
-
- Assert.Equal(SumPacketLengths(expected), SumPacketLengths(input));
-
- var output = MultiplexPacketList(isAsync, dataSize, input);
-
- ComparePacketLists(dataSize, expected, output);
- }
-
- [ConditionalTheory(nameof(IsUsingModernProcessSni)), MemberData(nameof(IsAsync))]
- public static void PassThroughMultiplePacketWithShortEnd(bool isAsync)
- {
- int dataSize = 40;
- List input = CreatePackets((dataSize, 20), 5, 6, 7, 8);
- List expected = input;
-
- Assert.Equal(SumPacketLengths(expected), SumPacketLengths(input));
-
- var output = MultiplexPacketList(isAsync, dataSize, input);
-
- ComparePacketLists(dataSize, expected, output);
- }
-
- [ConditionalTheory(nameof(IsUsingModernProcessSni)), MemberData(nameof(IsAsync))]
- public static void ReconstructSinglePacket(bool isAsync)
- {
- int dataSize = 4;
- var a = CreatePacket(dataSize, 0xF);
- List input = SplitPacket(a, 6);
- List expected = new List { a };
-
- Assert.Equal(SumPacketLengths(expected), SumPacketLengths(input));
-
- var output = MultiplexPacketList(isAsync, dataSize, input);
-
- ComparePacketLists(dataSize, expected, output);
- }
-
- [ConditionalTheory(nameof(IsUsingModernProcessSni)), MemberData(nameof(IsAsync))]
- public static void Reconstruct2Packets_Part_PartFull(bool isAsync)
- {
- int dataSize = 4;
- var expected = CreatePackets(dataSize, 0xAA, 0xBB);
-
- var input = SplitPackets(dataSize, expected,
- 6, // partial first packet
- (6 + 6), // end of packet 0, start of packet 1
- 6 // end of packet 1
- );
-
- Assert.Equal(SumPacketLengths(expected), SumPacketLengths(input));
-
- var output = MultiplexPacketList(isAsync, dataSize, input);
-
- ComparePacketLists(dataSize, expected, output);
- }
-
- [ConditionalTheory(nameof(IsUsingModernProcessSni)), MemberData(nameof(IsAsync))]
- public static void Reconstruct2Packets_Full_FullPart_Part(bool isAsync)
- {
- int dataSize = 30;
- var expected = new List { CreatePacket(30, 5), CreatePacket(10, 6), CreatePacket(30, 7) };
-
- var input = SplitPackets(38, expected,
- (8 + 30), // full
- (8 + 10) + (8 + 12), // full, part next
- 18 // part end
- );
-
- Assert.Equal(SumPacketLengths(expected), SumPacketLengths(input));
-
- var output = MultiplexPacketList(isAsync, dataSize, input);
-
- ComparePacketLists(dataSize, expected, output);
- }
-
- [ConditionalTheory(nameof(IsUsingModernProcessSni)), MemberData(nameof(IsAsync))]
- public static void ReconstructMultiplePacketSequence(bool isAsync)
- {
- int dataSize = 40;
- List expected = CreatePackets(dataSize, 5, 6, 7, 8);
- List input = SplitPackets(dataSize, expected,
- (8 + 40),
- (8 + 23),
- (17) + (8 + 23),
- (17) + (8 + 23),
- (17)
- );
-
- Assert.Equal(SumPacketLengths(expected), SumPacketLengths(input));
-
- var output = MultiplexPacketList(isAsync, dataSize, input);
-
- ComparePacketLists(dataSize, expected, output);
- }
-
- [ConditionalTheory(nameof(IsUsingModernProcessSni)), MemberData(nameof(IsAsync))]
- public static void ReconstructMultiplePacketSequenceWithShortEnd(bool isAsync)
- {
- int dataSize = 40;
- List expected = CreatePackets((dataSize, 20), 5, 6, 7, 8);
- List input = SplitPackets(dataSize, expected,
- (8 + 40),
- (8 + 23),
- (17) + (8 + 23),
- (17) + (8 + 20)
- );
-
- Assert.Equal(SumPacketLengths(expected), SumPacketLengths(input));
-
- var output = MultiplexPacketList(isAsync, dataSize, input);
-
- ComparePacketLists(dataSize, expected, output);
- }
-
- [ConditionalTheory(nameof(IsUsingModernProcessSni)), MemberData(nameof(IsAsync))]
- public static void Reconstruct3Packets_PartPartPart(bool isAsync)
- {
- int dataSize = 62;
-
- var expected = new List { CreatePacket(26, 5), CreatePacket(10, 6), CreatePacket(10, 7) };
-
- var input = SplitPackets(70, expected,
- (8 + 26) + (8 + 10) + (8 + 10) // = 70: full, full, part
- );
-
- var output = MultiplexPacketList(isAsync, dataSize, input);
-
- ComparePacketLists(dataSize, expected, output);
- }
-
- [ConditionalFact(nameof(IsUsingModernProcessSni))]
- public static void TrailingPartialPacketInSnapshotNotDuplicated()
- {
- int dataSize = 120;
-
- var expected = new List { CreatePacket(120, 5), CreatePacket(90, 6), CreatePacket(13, 7), };
-
- var input = SplitPackets(120, expected,
- (8 + 120),
- (8 + 90) + (8 + 13)
- );
-
- Assert.Equal(SumPacketLengths(expected), SumPacketLengths(input));
-
- var output = MultiplexPacketList(true, dataSize, input);
-
- ComparePacketLists(dataSize, expected, output);
- }
-
- [ConditionalFact(nameof(IsUsingModernProcessSni))]
- public static void BetweenAsyncAttentionPacket()
- {
- int dataSize = 120;
- var normalPacket = CreatePacket(120, 5);
- var attentionPacket = CreatePacket(13, 6);
- var input = new List { normalPacket, attentionPacket };
-
- using var stateObject = new TdsParserStateObject(input, TdsEnums.HEADER_LEN + dataSize, isAsync: true);
-
- for (int index = 0; index < input.Count; index++)
- {
- stateObject.Current = input[index];
- stateObject.ProcessSniPacket(default, 0);
- }
-
- Assert.NotNull(stateObject._inBuff);
- Assert.Equal(21, stateObject._inBytesRead);
- Assert.Equal(0, stateObject._inBytesUsed);
- Assert.NotNull(stateObject._snapshot);
- Assert.NotNull(stateObject._snapshot.List);
- Assert.Equal(2, stateObject._snapshot.List.Count);
-
- }
-
- [ConditionalFact(nameof(IsUsingModernProcessSni))]
- public static void MultipleFullPacketsInRemainderAreSplitCorrectly()
- {
- int dataSize = 800 - TdsEnums.HEADER_LEN;
- List expected = new List
- {
- CreatePacket(dataSize, 5), CreatePacket(80, 6), CreatePacket(21, 7)
- };
-
-
- List input = SplitPacket(CombinePackets(expected), 700);
-
- using var stateObject = new TdsParserStateObject(input, dataSize, isAsync: false);
-
- var output = MultiplexPacketList(false, dataSize, input);
-
- ComparePacketLists(dataSize, expected, output);
- }
-
- [ExcludeFromCodeCoverage]
- private static List MultiplexPacketList(bool isAsync, int dataSize, List input)
- {
- using var stateObject = new TdsParserStateObject(input, TdsEnums.HEADER_LEN + dataSize, isAsync);
- var output = new List();
-
- for (int index = 0; index < input.Count; index++)
- {
- stateObject.Current = input[index];
-
- stateObject.ProcessSniPacket(default, 0);
-
- if (stateObject._inBytesRead > 0)
- {
- if (
- stateObject._inBytesRead < TdsEnums.HEADER_LEN
- ||
- stateObject._inBytesRead != (TdsEnums.HEADER_LEN +
- Packet.GetDataLengthFromHeader(
- stateObject._inBuff.AsSpan(0, TdsEnums.HEADER_LEN)))
- )
- {
- Assert.Fail("incomplete packet exposed after call to ProcessSniPacket");
- }
-
- if (!isAsync)
- {
- output.Add(PacketData.Copy(stateObject._inBuff, stateObject._inBytesUsed,
- stateObject._inBytesRead));
- }
- }
- }
-
-
- if (!isAsync)
- {
- while (stateObject.PartialPacket != null)
- {
- stateObject.Current = default;
-
- stateObject.ProcessSniPacket(default, 0);
-
- if (stateObject._inBytesRead > 0)
- {
- if (
- stateObject._inBytesRead < TdsEnums.HEADER_LEN
- ||
- stateObject._inBytesRead != (TdsEnums.HEADER_LEN +
- Packet.GetDataLengthFromHeader(
- stateObject._inBuff.AsSpan(0, TdsEnums.HEADER_LEN)))
- )
- {
- Assert.Fail(
- "incomplete packet exposed after call to ProcessSniPacket with usePartialPacket");
- }
-
- output.Add(PacketData.Copy(stateObject._inBuff, stateObject._inBytesUsed,
- stateObject._inBytesRead));
- }
- }
-
- }
- else
- {
- output = stateObject._snapshot.List;
- }
-
- return output;
- }
-
- [ExcludeFromCodeCoverage]
- private static void ComparePacketLists(int dataSize, List expected, List output)
- {
- Assert.NotNull(expected);
- Assert.NotNull(output);
- Assert.Equal(expected.Count, output.Count);
-
- for (int index = 0; index < expected.Count; index++)
- {
- var a = expected[index];
- var b = output[index];
-
- var compare = a.AsSpan().SequenceCompareTo(b.AsSpan());
-
- if (compare != 0)
- {
- Assert.Fail($"expected data does not match output data at packet index {index}");
- }
- }
- }
-
- [ExcludeFromCodeCoverage]
- public static PacketData CreatePacket(int dataSize, byte dataValue, int startOffset = 0, int endPadding = 0)
- {
- byte[] buffer = new byte[startOffset + TdsEnums.HEADER_LEN + dataSize + endPadding];
- Span packet = buffer.AsSpan(startOffset, TdsEnums.HEADER_LEN + dataSize);
- WritePacket(packet, dataSize, dataValue, 1);
- return new PacketData(buffer, startOffset, buffer.Length - endPadding);
- }
-
- [ExcludeFromCodeCoverage]
- public static List CreatePackets(DataSize sizes, params byte[] dataValues)
- {
- int count = dataValues.Length;
- List list = new List(count);
-
- for (byte index = 0; index < count; index++)
- {
- int dataSize = sizes.GetSize(index == dataValues.Length - 1);
- int packetSize = TdsEnums.HEADER_LEN + dataSize;
- byte[] array = new byte[packetSize];
- WritePacket(array, dataSize, dataValues[index], index);
- list.Add(new PacketData(array, 0, packetSize));
- }
-
- return list;
- }
-
- [ExcludeFromCodeCoverage]
- private static void WritePacket(Span buffer, int dataSize, byte dataValue, byte id)
- {
- Span header = buffer.Slice(0, TdsEnums.HEADER_LEN);
- header[0] = 4; // Type, 4 - Raw Data
- header[1] = 0; // Status, 0 - normal message
- BinaryPrimitives.TryWriteInt16BigEndian(header.Slice(TdsEnums.HEADER_LEN_FIELD_OFFSET, 2),
- (short)(TdsEnums.HEADER_LEN + dataSize)); // total length
- BinaryPrimitives.TryWriteInt16BigEndian(header.Slice(TdsEnums.SPID_OFFSET, 2), short.MaxValue); // SPID
- header[TdsEnums.HEADER_LEN_FIELD_OFFSET + 4] = id; // PacketID
- header[TdsEnums.HEADER_LEN_FIELD_OFFSET + 5] = 0; // Window
-
- Span data = buffer.Slice(TdsEnums.HEADER_LEN, dataSize);
- data.Fill(dataValue);
- }
-
- [ExcludeFromCodeCoverage]
- public static List SplitPacket(PacketData packet, int length)
- {
- List list = new List(2);
- while (packet.Length > length)
- {
- list.Add(new PacketData(packet.Array, packet.Start, length));
- packet = new PacketData(packet.Array, packet.Start + length, packet.Length - length);
- }
-
- if (packet.Length > 0)
- {
- list.Add(packet);
- }
-
- return list;
- }
-
- [ExcludeFromCodeCoverage]
- public static List SplitPackets(int dataSize, List packets, params int[] lengths)
- {
- List list = new List(lengths.Length);
- int packetSize = TdsEnums.HEADER_LEN + dataSize;
- byte[][] arrays = new byte[lengths.Length][];
- for (int index = 0; index < lengths.Length; index++)
- {
- if (lengths[index] > packetSize)
- {
- throw new ArgumentOutOfRangeException(
- $"segment size of an individual part cannot exceed the packet buffer size of the state object, max packet size: {packetSize}, supplied length: {lengths[index]}, at index: {index}");
- }
-
- arrays[index] = new byte[lengths[index]];
- }
-
- int targetOffset = 0;
- int targetIndex = 0;
-
- int sourceOffset = 0;
- int sourceIndex = 0;
-
-
- do
- {
- Span targetSpan = Span.Empty;
- if (targetOffset < arrays[targetIndex].Length)
- {
- targetSpan = arrays[targetIndex].AsSpan(targetOffset);
- }
- else
- {
- targetIndex += 1;
- targetOffset = 0;
- continue;
- }
-
- Span sourceSpan = Span.Empty;
- if (sourceOffset < packets[sourceIndex].Length)
- {
- sourceSpan = packets[sourceIndex].AsSpan(sourceOffset);
- }
- else
- {
- sourceIndex += 1;
- sourceOffset = 0;
- continue;
- }
-
- int copy = Math.Min(targetSpan.Length, sourceSpan.Length);
- if (copy > 0)
- {
- targetOffset += copy;
- sourceOffset += copy;
- sourceSpan.Slice(0, copy).CopyTo(targetSpan.Slice(0, copy));
- }
- } while (sourceIndex < packets.Count && targetIndex < arrays.Length);
-
- foreach (var array in arrays)
- {
- list.Add(new PacketData(array, 0, array.Length));
- }
-
- return list;
- }
-
- [ExcludeFromCodeCoverage]
- public static PacketData CombinePackets(List packets)
- {
- int totalLength = SumPacketLengths(packets);
- byte[] buffer = new byte[totalLength];
- int offset = 0;
- for (int index = 0; index < packets.Count; index++)
- {
- PacketData packet = packets[index];
- Array.Copy(packet.Array, packet.Start, buffer, offset, packet.Length);
- offset += packet.Length;
- }
-
- return new PacketData(buffer, 0, totalLength);
- }
-
- [ExcludeFromCodeCoverage]
- public static int PacketSizeFromDataSize(int dataSize) => TdsEnums.HEADER_LEN + dataSize;
-
- [ExcludeFromCodeCoverage]
- public static int DataSizeFromPacketSize(int packetSize) => packetSize - TdsEnums.HEADER_LEN;
-
- [ExcludeFromCodeCoverage]
- public static int SumPacketLengths(List list)
- {
- int total = 0;
- for (int index = 0; index < list.Count; index++)
- {
- total += list[index].Length;
- }
- return total;
- }
-
- [ExcludeFromCodeCoverage]
- public static List LoadPacketBinFiles(string directoryName)
- {
- // expects a set of files contained in a directory with the name
- // formatted as packet_{number}_{dataSize}.bin each packet will be
- // loaded into a byte[]
-
- string[] files = Directory.GetFiles(directoryName, "packet*.bin", SearchOption.TopDirectoryOnly);
- SortedDictionary packets = new SortedDictionary();
- foreach (string file in files)
- {
- Match match = Regex.Match(file, @"packet_(?\d+)_(?\d+)\.bin");
- int number = int.Parse(match.Groups["number"].Value);
- int size = int.Parse(match.Groups["size"].Value);
- packets.Add(
- number,
- new PacketData(
- System.IO.File.ReadAllBytes(file),
- 0,
- size
- )
- );
- }
-
- return packets.Values.ToList();
- }
-
- [ExcludeFromCodeCoverage]
- public static List NaiveReconstructPacketStream(List input)
- {
- int dataSize = input[0].Array.Length;
- List output = new List(input.Count);
-
- byte[] currentBuffer = new byte[dataSize];
- int currentBufferOffset = 0;
-
- foreach (PacketData inputPacket in input)
- {
- int inputPacketOffset = 0;
- while (inputPacketOffset < inputPacket.Length)
- {
- if (currentBufferOffset < dataSize)
- {
- int requiredCount = dataSize - currentBufferOffset;
- int availableCount = inputPacket.Length - inputPacketOffset;
- int copyCount = Math.Min(requiredCount, availableCount);
- ReadOnlySpan copyFrom = inputPacket.Array.AsSpan(inputPacketOffset, copyCount);
- Span copyTo = currentBuffer.AsSpan(currentBufferOffset, copyCount);
- copyFrom.CopyTo(copyTo);
- currentBufferOffset += copyCount;
- inputPacketOffset += copyCount;
- }
-
- if (currentBufferOffset == dataSize)
- {
- output.Add(new PacketData(currentBuffer, 0, dataSize));
- currentBufferOffset = 0;
- currentBuffer = new byte[dataSize];
- }
- }
- }
-
- if (currentBufferOffset > 0)
- {
- output.Add(new PacketData(currentBuffer, 0, currentBufferOffset));
- }
-
- for (int index = 0; index < output.Count; index++)
- {
- PacketData packet = output[index];
- int expectedLength = 8 + Packet.GetDataLengthFromHeader(packet.Array);
- if (expectedLength != packet.Length)
- {
- if (index != output.Count - 1)
- {
- throw new InvalidOperationException(
- "non-terminal packet has a length mismatch between the packet header and amount of data available");
- }
- else
- {
- byte[] remainder = new byte[dataSize];
- int remainderSize = packet.Length - expectedLength;
- Span copyFrom = packet.Array.AsSpan(expectedLength, remainderSize);
- Span copyTo = remainder.AsSpan(0, remainderSize);
- copyFrom.CopyTo(copyTo);
- copyFrom.Fill(0);
-
- PacketData replacementPacket = new PacketData(packet.Array, 0, expectedLength);
- PacketData additionalPacket = new PacketData(remainder, 0, remainderSize);
- output[index] = replacementPacket;
- output.Add(additionalPacket);
- }
- }
- }
-
- return output;
- }
- }
-
- [ExcludeFromCodeCoverage]
- [DebuggerDisplay("{ToDebugString(),nq}")]
- public readonly struct PacketData
- {
- public readonly byte[] Array;
- public readonly int Start;
- public readonly int Length;
-
- public PacketData(byte[] array, int start, int length)
- {
- Array = array;
- Start = start;
- Length = length;
- }
-
- public Span AsSpan()
- {
- return Array == null ? Span.Empty : Array.AsSpan(Start, Length);
- }
-
- public Span AsSpan(int start)
- {
- Span span = AsSpan();
- return span.Slice(start);
- }
-
- public static PacketData Copy(byte[] array, int start, int length)
- {
- byte[] newArray = null;
- if (array != null)
- {
- newArray = new byte[array.Length];
- Buffer.BlockCopy(array, start, newArray, start, length);
- }
-
- return new PacketData(newArray, start, length);
- }
-
- [ExcludeFromCodeCoverage]
- public string ToDebugString()
- {
- StringBuilder buffer = new StringBuilder(128);
- buffer.Append(Length);
-
- if (Array != null && Array.Length > 0)
- {
- if (Array.Length != Length)
- {
- buffer.AppendFormat(" (arr: {0})", Array.Length);
- }
-
- buffer.Append(": {");
- buffer.AppendFormat("{0:D2}", Array[0]);
-
- int max = Math.Min(32, Array.Length);
- for (int index = 1; index < max; index++)
- {
- buffer.Append(',');
- buffer.AppendFormat("{0:D2}", Array[index]);
- }
-
- if (Length > max)
- {
- buffer.Append(" ...");
- }
-
- buffer.Append('}');
- }
-
- return buffer.ToString();
- }
-
- }
-
- [ExcludeFromCodeCoverage]
- [DebuggerStepThrough]
- public struct DataSize
- {
- public DataSize(int commonSize)
- {
- CommonSize = commonSize;
- LastSize = commonSize;
- }
-
- public DataSize(int commonSize, int lastSize)
- {
- CommonSize = commonSize;
- LastSize = lastSize;
- }
-
- public int LastSize { get; set; }
- public int CommonSize { get; set; }
-
- public int GetSize(bool isLast)
- {
- if (isLast)
- {
- return LastSize;
- }
- else
- {
- return CommonSize;
- }
- }
-
- public static implicit operator DataSize(int commonSize)
- {
- return new DataSize(commonSize, commonSize);
- }
-
- public static implicit operator DataSize((int commonSize, int lastSize) values)
- {
- return new DataSize(values.commonSize, values.lastSize);
- }
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs
index d41f4b40d1..c15f1a9300 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using Microsoft.Data.SqlClient.FunctionalTests.DataCommon;
using Xunit;
namespace Microsoft.Data.SqlClient.Tests
@@ -11,7 +12,6 @@ public class SqlAuthenticationProviderTest
[Theory]
[InlineData(SqlAuthenticationMethod.ActiveDirectoryIntegrated)]
[InlineData(SqlAuthenticationMethod.ActiveDirectoryPassword)]
- [InlineData(SqlAuthenticationMethod.ActiveDirectoryInteractive)]
[InlineData(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal)]
[InlineData(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)]
[InlineData(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity)]
@@ -22,5 +22,18 @@ public void DefaultAuthenticationProviders(SqlAuthenticationMethod method)
{
Assert.IsType(SqlAuthenticationProvider.GetProvider(method));
}
+
+ #if NETFRAMEWORK
+ // This test is only valid for .NET Framework
+
+ // Overridden by app.config in this project
+ [Theory]
+ [InlineData(SqlAuthenticationMethod.ActiveDirectoryInteractive)]
+ public void DefaultAuthenticationProviders_Interactive(SqlAuthenticationMethod method)
+ {
+ Assert.IsType(SqlAuthenticationProvider.GetProvider(method));
+ }
+
+ #endif
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionReadOnlyRoutingTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionReadOnlyRoutingTests.cs
deleted file mode 100644
index c3574dbc13..0000000000
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionReadOnlyRoutingTests.cs
+++ /dev/null
@@ -1,140 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Collections.Generic;
-using System.Net;
-using System.Threading.Tasks;
-using Microsoft.SqlServer.TDS.Servers;
-using Xunit;
-
-namespace Microsoft.Data.SqlClient.Tests
-{
- public class SqlConnectionReadOnlyRoutingTests
- {
- [Fact]
- public void NonRoutedConnection()
- {
- using TestTdsServer server = TestTdsServer.StartTestServer();
- SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(server.ConnectionString) { ApplicationIntent = ApplicationIntent.ReadOnly };
- using SqlConnection connection = new SqlConnection(builder.ConnectionString);
- connection.Open();
- }
-
- [Fact]
- public async Task NonRoutedAsyncConnection()
- {
- using TestTdsServer server = TestTdsServer.StartTestServer();
- SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(server.ConnectionString) { ApplicationIntent = ApplicationIntent.ReadOnly };
- using SqlConnection connection = new SqlConnection(builder.ConnectionString);
- await connection.OpenAsync();
- }
-
- [Fact]
- public void RoutedConnection()
- => RecursivelyRoutedConnection(1);
-
- [Fact]
- public async Task RoutedAsyncConnection()
- => await RecursivelyRoutedAsyncConnection(1);
-
- [Theory]
- [InlineData(2)]
- [InlineData(9)]
- [InlineData(11)] // The driver rejects more than 10 redirects (11 layers of redirecting servers)
- public void RecursivelyRoutedConnection(int layers)
- {
- TestTdsServer innerServer = TestTdsServer.StartTestServer();
- IPEndPoint lastEndpoint = innerServer.Endpoint;
- Stack routingLayers = new(layers + 1);
- string lastConnectionString = innerServer.ConnectionString;
-
- try
- {
- routingLayers.Push(innerServer);
- for (int i = 0; i < layers; i++)
- {
- TestRoutingTdsServer router = TestRoutingTdsServer.StartTestServer(lastEndpoint);
-
- routingLayers.Push(router);
- lastEndpoint = router.Endpoint;
- lastConnectionString = router.ConnectionString;
- }
-
- SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(lastConnectionString) { ApplicationIntent = ApplicationIntent.ReadOnly };
- using SqlConnection connection = new SqlConnection(builder.ConnectionString);
- connection.Open();
- }
- finally
- {
- while (routingLayers.Count > 0)
- {
- GenericTDSServer layer = routingLayers.Pop();
-
- if (layer is IDisposable disp)
- {
- disp.Dispose();
- }
- }
- }
- }
-
- [Theory]
- [InlineData(2)]
- [InlineData(9)]
- [InlineData(11)] // The driver rejects more than 10 redirects (11 layers of redirecting servers)
- public async Task RecursivelyRoutedAsyncConnection(int layers)
- {
- TestTdsServer innerServer = TestTdsServer.StartTestServer();
- IPEndPoint lastEndpoint = innerServer.Endpoint;
- Stack routingLayers = new(layers + 1);
- string lastConnectionString = innerServer.ConnectionString;
-
- try
- {
- routingLayers.Push(innerServer);
- for (int i = 0; i < layers; i++)
- {
- TestRoutingTdsServer router = TestRoutingTdsServer.StartTestServer(lastEndpoint);
-
- routingLayers.Push(router);
- lastEndpoint = router.Endpoint;
- lastConnectionString = router.ConnectionString;
- }
-
- SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(lastConnectionString) { ApplicationIntent = ApplicationIntent.ReadOnly };
- using SqlConnection connection = new SqlConnection(builder.ConnectionString);
- await connection.OpenAsync();
- }
- finally
- {
- while (routingLayers.Count > 0)
- {
- GenericTDSServer layer = routingLayers.Pop();
-
- if (layer is IDisposable disp)
- {
- disp.Dispose();
- }
- }
- }
- }
-
- [Fact]
- public void ConnectionRoutingLimit()
- {
- SqlException sqlEx = Assert.Throws(() => RecursivelyRoutedConnection(12)); // This will fail on the 11th redirect
-
- Assert.Contains("Too many redirections have occurred.", sqlEx.Message, StringComparison.InvariantCultureIgnoreCase);
- }
-
- [Fact]
- public async Task AsyncConnectionRoutingLimit()
- {
- SqlException sqlEx = await Assert.ThrowsAsync(() => RecursivelyRoutedAsyncConnection(12)); // This will fail on the 11th redirect
-
- Assert.Contains("Too many redirections have occurred.", sqlEx.Message, StringComparison.InvariantCultureIgnoreCase);
- }
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs
deleted file mode 100644
index 89807b0132..0000000000
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs
+++ /dev/null
@@ -1,198 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Reflection;
-using Microsoft.Data.SqlClient.Tests;
-
-using SwitchesHelper = Microsoft.Data.SqlClient.Tests.Common.LocalAppContextSwitchesHelper;
-
-namespace Microsoft.Data.SqlClient
-{
- internal struct PacketHandle
- {
- }
-
- internal partial class TdsParserStateObject : IDisposable
- {
- internal int ObjectID = 1;
-
- internal class SQL
- {
- internal static Exception InvalidInternalPacketSize(string v) => throw new Exception(v ?? nameof(InvalidInternalPacketSize));
-
- internal static Exception ParsingError(ParsingErrorState state) => throw new Exception(state.ToString());
- }
-
- internal static class SqlClientEventSource
- {
- internal static class Log
- {
- internal static void TryAdvancedTraceBinEvent(string message, params object[] values)
- {
- }
- }
- }
-
- private enum SnapshotStatus
- {
- NotActive,
- ReplayStarting,
- ReplayRunning
- }
-
- internal enum TdsParserState
- {
- Closed,
- OpenNotLoggedIn,
- OpenLoggedIn,
- Broken,
- }
-
- private uint GetSniPacket(PacketHandle packet, ref uint dataSize)
- {
- return SniPacketGetData(packet, _inBuff, ref dataSize);
- }
-
- private class StringsHelper
- {
- internal static string GetString(string sqlMisc_InvalidArraySizeMessage) => Strings.SqlMisc_InvalidArraySizeMessage;
- }
-
- internal class Strings
- {
- internal static string SqlMisc_InvalidArraySizeMessage = nameof(SqlMisc_InvalidArraySizeMessage);
-
- }
-
- public class Parser
- {
- internal object ProcessSNIError(TdsParserStateObject tdsParserStateObject) => "ProcessSNIError";
- public TdsParserState State = TdsParserState.OpenLoggedIn;
- }
-
- sealed internal class LastIOTimer
- {
- internal long _value;
- }
-
- internal sealed class Snapshot
- {
- public List List;
-
- public Snapshot() => List = new List();
- [DebuggerStepThrough]
- internal void AssertCurrent() { }
- [DebuggerStepThrough]
- internal void AppendPacketData(byte[] buffer, int read) => List.Add(new PacketData(buffer, 0, read));
- [DebuggerStepThrough]
- internal void MoveNext()
- {
-
- }
- }
-
- public List Input;
- public PacketData Current;
- public bool IsAsync { get => _snapshot != null; }
-
- public int _packetSize;
-
- internal Snapshot _snapshot;
- public int _inBytesRead;
- public int _inBytesUsed;
- public byte[] _inBuff;
-
- [DebuggerStepThrough]
- public TdsParserStateObject(List input, int packetSize, bool isAsync)
- {
- _packetSize = packetSize;
- _inBuff = new byte[_packetSize];
- Input = input;
- if (isAsync)
- {
- _snapshot = new Snapshot();
- }
- }
-
- [DebuggerStepThrough]
- public void Dispose()
- {
- LocalAppContextSwitches.Dispose();
- }
-
- [DebuggerStepThrough]
- private uint SniPacketGetData(PacketHandle packet, byte[] inBuff, ref uint dataSize)
- {
- Span target = inBuff.AsSpan(0, _packetSize);
- Span source = Current.Array.AsSpan(Current.Start, Current.Length);
- source.CopyTo(target);
- dataSize = (uint)Current.Length;
- return TdsEnums.SNI_SUCCESS;
- }
-
- [DebuggerStepThrough]
- void SetBuffer(byte[] buffer, int inBytesUsed, int inBytesRead)
- {
- _inBuff = buffer;
- _inBytesUsed = inBytesUsed;
- _inBytesRead = inBytesRead;
- }
-
- // stubs
- private LastIOTimer _lastSuccessfulIOTimer = new LastIOTimer();
- private Parser _parser = new Parser();
- private SnapshotStatus _snapshotStatus = SnapshotStatus.NotActive;
-
- [DebuggerStepThrough]
- private void SniReadStatisticsAndTracing() { }
- [DebuggerStepThrough]
- private void AssertValidState() { }
-
- [DebuggerStepThrough]
- private void AddError(object value) => throw new Exception(value as string ?? "AddError");
-
- private SwitchesHelper LocalAppContextSwitches = new();
-
-#if NETFRAMEWORK
- private SniNativeWrapperImpl _native;
- internal SniNativeWrapperImpl SniNativeWrapper
- {
- get
- {
- if (_native == null)
- {
- _native = new SniNativeWrapperImpl(this);
- }
- return _native;
- }
- }
-
- internal class SniNativeWrapperImpl
- {
- private readonly TdsParserStateObject _parent;
- internal SniNativeWrapperImpl(TdsParserStateObject parent) => _parent = parent;
-
- internal uint SniPacketGetData(PacketHandle packet, byte[] inBuff, ref uint dataSize) => _parent.SniPacketGetData(packet, inBuff, ref dataSize);
- }
-#endif
- }
-
- internal static class TdsEnums
- {
- public const uint SNI_SUCCESS = 0; // The operation completed successfully.
- // header constants
- public const int HEADER_LEN = 8;
- public const int HEADER_LEN_FIELD_OFFSET = 2;
- public const int SPID_OFFSET = 4;
- }
-
- internal enum ParsingErrorState
- {
- CorruptedTdsStream = 18,
- ProcessSniPacketFailed = 19,
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestRoutingTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestRoutingTdsServer.cs
deleted file mode 100644
index 130b50cad9..0000000000
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestRoutingTdsServer.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Net;
-using System.Runtime.CompilerServices;
-using Microsoft.SqlServer.TDS.EndPoint;
-using Microsoft.SqlServer.TDS.Servers;
-
-namespace Microsoft.Data.SqlClient.Tests
-{
- internal class TestRoutingTdsServer : RoutingTDSServer, IDisposable
- {
- private const int DefaultConnectionTimeout = 5;
-
- private TDSServerEndPoint _endpoint = null;
-
- private SqlConnectionStringBuilder _connectionStringBuilder;
-
- public TestRoutingTdsServer(RoutingTDSServerArguments args) : base(args) { }
-
- public static TestRoutingTdsServer StartTestServer(IPEndPoint destinationEndpoint, bool enableFedAuth = false, bool enableLog = false, int connectionTimeout = DefaultConnectionTimeout, bool excludeEncryption = false, [CallerMemberName] string methodName = "")
- {
- RoutingTDSServerArguments args = new RoutingTDSServerArguments()
- {
- Log = enableLog ? Console.Out : null,
- RoutingTCPHost = destinationEndpoint.Address.ToString() == IPAddress.Any.ToString() ? IPAddress.Loopback.ToString() : destinationEndpoint.Address.ToString(),
- RoutingTCPPort = (ushort)destinationEndpoint.Port,
- };
-
- if (enableFedAuth)
- {
- args.FedAuthRequiredPreLoginOption = SqlServer.TDS.PreLogin.TdsPreLoginFedAuthRequiredOption.FedAuthRequired;
- }
- if (excludeEncryption)
- {
- args.Encryption = SqlServer.TDS.PreLogin.TDSPreLoginTokenEncryptionType.None;
- }
-
- TestRoutingTdsServer server = new TestRoutingTdsServer(args);
- server._endpoint = new TDSServerEndPoint(server) { ServerEndPoint = new IPEndPoint(IPAddress.Any, 0) };
- server._endpoint.EndpointName = methodName;
- // The server EventLog should be enabled as it logs the exceptions.
- server._endpoint.EventLog = enableLog ? Console.Out : null;
- server._endpoint.Start();
-
- int port = server._endpoint.ServerEndPoint.Port;
- server._connectionStringBuilder = excludeEncryption
- // Allow encryption to be set when encryption is to be excluded from pre-login response.
- ? new SqlConnectionStringBuilder() { DataSource = "localhost," + port, ConnectTimeout = connectionTimeout, Encrypt = SqlConnectionEncryptOption.Mandatory }
- : new SqlConnectionStringBuilder() { DataSource = "localhost," + port, ConnectTimeout = connectionTimeout, Encrypt = SqlConnectionEncryptOption.Optional };
- server.ConnectionString = server._connectionStringBuilder.ConnectionString;
- server.Endpoint = server._endpoint.ServerEndPoint;
- return server;
- }
-
- public void Dispose() => _endpoint?.Stop();
-
- public string ConnectionString { get; private set; }
-
- public IPEndPoint Endpoint { get; private set; }
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs
deleted file mode 100644
index a5976fd6d5..0000000000
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Net;
-using System.Runtime.CompilerServices;
-using Microsoft.SqlServer.TDS.EndPoint;
-using Microsoft.SqlServer.TDS.Servers;
-
-namespace Microsoft.Data.SqlClient.Tests
-{
- internal class TestTdsServer : GenericTDSServer, IDisposable
- {
- private const int DefaultConnectionTimeout = 5;
-
- private TDSServerEndPoint _endpoint = null;
-
- private SqlConnectionStringBuilder _connectionStringBuilder;
-
- public TestTdsServer(TDSServerArguments args) : base(args) { }
-
- public TestTdsServer(QueryEngine engine, TDSServerArguments args) : base(args)
- {
- Engine = engine;
- }
-
- public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool enableFedAuth = false, bool enableLog = false, int connectionTimeout = DefaultConnectionTimeout, bool excludeEncryption = false, Version serverVersion = null, [CallerMemberName] string methodName = "")
- {
- TDSServerArguments args = new TDSServerArguments()
- {
- Log = enableLog ? Console.Out : null,
- };
-
- if (enableFedAuth)
- {
- args.FedAuthRequiredPreLoginOption = SqlServer.TDS.PreLogin.TdsPreLoginFedAuthRequiredOption.FedAuthRequired;
- }
- if (excludeEncryption)
- {
- args.Encryption = SqlServer.TDS.PreLogin.TDSPreLoginTokenEncryptionType.None;
- }
- if (serverVersion != null)
- {
- args.ServerVersion = serverVersion;
- }
-
- TestTdsServer server = engine == null ? new TestTdsServer(args) : new TestTdsServer(engine, args);
- server._endpoint = new TDSServerEndPoint(server) { ServerEndPoint = new IPEndPoint(IPAddress.Any, 0) };
- server._endpoint.EndpointName = methodName;
- // The server EventLog should be enabled as it logs the exceptions.
- server._endpoint.EventLog = enableLog ? Console.Out : null;
- server._endpoint.Start();
-
- int port = server._endpoint.ServerEndPoint.Port;
- server._connectionStringBuilder = excludeEncryption
- // Allow encryption to be set when encryption is to be excluded from pre-login response.
- ? new SqlConnectionStringBuilder() { DataSource = "localhost," + port, ConnectTimeout = connectionTimeout, Encrypt = SqlConnectionEncryptOption.Mandatory }
- : new SqlConnectionStringBuilder() { DataSource = "localhost," + port, ConnectTimeout = connectionTimeout, Encrypt = SqlConnectionEncryptOption.Optional };
- server.ConnectionString = server._connectionStringBuilder.ConnectionString;
- server.Endpoint = server._endpoint.ServerEndPoint;
- return server;
- }
-
- public static TestTdsServer StartTestServer(bool enableFedAuth = false, bool enableLog = false, int connectionTimeout = DefaultConnectionTimeout, bool excludeEncryption = false, Version serverVersion = null, [CallerMemberName] string methodName = "")
- {
- return StartServerWithQueryEngine(null, enableFedAuth, enableLog, connectionTimeout, excludeEncryption, serverVersion, methodName);
- }
-
- public void Dispose() => _endpoint?.Stop();
-
- public string ConnectionString { get; private set; }
-
- public IPEndPoint Endpoint { get; private set; }
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/app.config b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/app.config
new file mode 100644
index 0000000000..fb7f63f65f
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/app.config
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs
index 184b5465ec..d3c40e2d34 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs
@@ -45,6 +45,24 @@ public sealed class ApiShould : IClassFixture, IDis
"'MSSQL_CERTIFICATE_STORE', 'MSSQL_CNG_STORE', 'MSSQL_CSP_PROVIDER'",
$"'{NotRequiredProviderName}'");
+ private HashSet _cancellationExceptionMessages = new HashSet()
+ {
+ "A severe error occurred on the current command. " +
+ "The results, if any, should be discarded." + Environment.NewLine +
+ "Operation cancelled by user.",
+
+ "A severe error occurred on the current command. " +
+ "The results, if any, should be discarded.",
+
+ "Operation cancelled by user.",
+
+ "The request failed to run because the batch is aborted, " +
+ "this can be caused by abort signal sent from client, " +
+ "or another request is running in the same session, " +
+ "which makes the session busy." + Environment.NewLine +
+ "Operation cancelled by user."
+ };
+
public ApiShould(PlatformSpecificTestContext context)
{
_fixture = context.Fixture;
@@ -148,8 +166,8 @@ public void SqlParameterProperties(string connection)
const string firstColumnName = @"firstColumn";
const string secondColumnName = @"secondColumn";
const string thirdColumnName = @"thirdColumn";
- string inputProcedureName = DataTestUtility.GetUniqueName("InputProc").ToString();
- string outputProcedureName = DataTestUtility.GetUniqueName("OutputProc").ToString();
+ string inputProcedureName = DataTestUtility.GetShortName("InputProc").ToString();
+ string outputProcedureName = DataTestUtility.GetShortName("OutputProc").ToString();
const int charColumnSize = 100;
const int decimalColumnPrecision = 10;
const int decimalColumnScale = 4;
@@ -704,7 +722,7 @@ public void TestExecuteReader(string connection)
[ClassData(typeof(AEConnectionStringProvider))]
public async Task TestExecuteReaderAsyncWithLargeQuery(string connectionString)
{
- string randomName = DataTestUtility.GetUniqueName(Guid.NewGuid().ToString().Replace("-", ""), false);
+ string randomName = DataTestUtility.GetShortName(Guid.NewGuid().ToString().Replace("-", ""), false);
if (randomName.Length > 50)
{
randomName = randomName.Substring(0, 50);
@@ -894,8 +912,8 @@ public void TestEnclaveStoredProceduresWithAndWithoutParameters(string connectio
using SqlCommand sqlCommand = new("", sqlConnection, transaction: null,
columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled);
- string procWithoutParams = DataTestUtility.GetUniqueName("EnclaveWithoutParams", withBracket: false);
- string procWithParam = DataTestUtility.GetUniqueName("EnclaveWithParams", withBracket: false);
+ string procWithoutParams = DataTestUtility.GetShortName("EnclaveWithoutParams", withBracket: false);
+ string procWithParam = DataTestUtility.GetShortName("EnclaveWithParams", withBracket: false);
try
{
@@ -1983,19 +2001,15 @@ public void TestBeginAndEndExecuteReaderWithAsyncCallback(string connection, Com
}
}
- [SkipOnTargetFramework(TargetFrameworkMonikers.Netcoreapp)]
[ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))]
[ClassData(typeof(AEConnectionStringProviderWithExecutionMethod))]
- public void TestSqlCommandCancel(string connection, string value, int number)
+ public void TestSqlCommandCancel(string connection, string value)
{
CleanUpTable(connection, _tableName);
string executeMethod = value;
Assert.True(!string.IsNullOrWhiteSpace(executeMethod), @"executeMethod should not be null or empty");
- int numberOfCancelCalls = number;
- Assert.True(numberOfCancelCalls >= 0, "numberofCancelCalls should be >=0.");
-
IList values = GetValues(dataHint: 58);
Assert.True(values != null && values.Count >= 3, @"values should not be null and count should be >= 3.");
@@ -2007,7 +2021,10 @@ public void TestSqlCommandCancel(string connection, string value, int number)
{
sqlConnection.Open();
- using (SqlCommand sqlCommand = new SqlCommand($@"SELECT * FROM [{_tableName}] WHERE FirstName = @FirstName AND CustomerId = @CustomerId",
+ // WAITFOR DELAY is present to ensure that the command is always executing by the time the cancellation occurs.
+ // Without this, a command which has completed by the time the cancellation occurs will fail the test. It's last
+ // in the command to ensure that cancellation is tested while row data or metadata is flowing.
+ using (SqlCommand sqlCommand = new SqlCommand($@"SELECT * FROM [{_tableName}] WHERE FirstName = @FirstName AND CustomerId = @CustomerId; WAITFOR DELAY '00:00:00.150'",
sqlConnection,
transaction: null,
columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled))
@@ -2017,29 +2034,50 @@ public void TestSqlCommandCancel(string connection, string value, int number)
CommandHelper.s_sleepDuringTryFetchInputParameterEncryptionInfo?.SetValue(null, true);
- Thread[] threads = new Thread[2];
-
- // Invoke ExecuteReader or ExecuteNonQuery in another thread.
+ Task[] tasks = new Task[2];
+ ManualResetEventSlim startWorkloadSignal = new ManualResetEventSlim(false);
+ ManualResetEventSlim workloadCompleteSignal = new ManualResetEventSlim(false);
+
+ /*
+ * Invoke ExecuteReader or ExecuteNonQuery in another thread.
+ * Use long-running tasks to create the thread. This enables any failed assertions to propagate, rather than
+ * allowing the exception to kill the thread and the process.
+ * These threads should progress in the sequence below:
+ *
+ * Workload Thread | Cancel Thread
+ * ------------------------------------ | -------------
+ * Start thread | Start thread
+ * Wait for signal | -
+ * - | Set signal for workload start
+ * Start workload execution | Loop, cancelling workload until workload complete signal set
+ * Throw cancellation exception | -
+ * Set signal for workload complete | -
+ * End thread | Finish loop and end thread
+ */
if (executeMethod == @"ExecuteReader")
{
- threads[0] = new Thread(new ParameterizedThreadStart(Thread_ExecuteReader));
+ tasks[0] = new Task(Thread_ExecuteReader,
+ new TestCommandCancelParams(sqlCommand, _tableName, startWorkloadSignal, workloadCompleteSignal),
+ TaskCreationOptions.LongRunning);
}
else
{
- threads[0] = new Thread(new ParameterizedThreadStart(Thread_ExecuteNonQuery));
+ tasks[0] = new Task(Thread_ExecuteNonQuery,
+ new TestCommandCancelParams(sqlCommand, _tableName, startWorkloadSignal, workloadCompleteSignal),
+ TaskCreationOptions.LongRunning);
}
-
- threads[1] = new Thread(new ParameterizedThreadStart(Thread_Cancel));
+ tasks[1] = new Task(Thread_Cancel,
+ new TestCommandCancelParams(sqlCommand, _tableName, startWorkloadSignal, workloadCompleteSignal),
+ TaskCreationOptions.LongRunning);
// Start the execute thread.
- threads[0].Start(new TestCommandCancelParams(sqlCommand, _tableName, numberOfCancelCalls));
+ tasks[0].Start();
// Start the thread which cancels the above command started by the execute thread.
- threads[1].Start(new TestCommandCancelParams(sqlCommand, _tableName, numberOfCancelCalls));
+ tasks[1].Start();
// Wait for the threads to finish.
- threads[0].Join();
- threads[1].Join();
+ Task.WaitAll(tasks);
CommandHelper.s_sleepDuringTryFetchInputParameterEncryptionInfo?.SetValue(null, false);
@@ -2061,37 +2099,48 @@ public void TestSqlCommandCancel(string connection, string value, int number)
Assert.True(rowsAffected == numberOfRows, "Unexpected number of rows affected as returned by EndExecuteReader.");
- // Verify the state of the sql command object.
+ // Verify the state of the sql command object. Also ensure that the exit lock was set (and didn't time out.)
VerifySqlCommandStateAfterCompletionOrCancel(sqlCommand);
+ Assert.True(workloadCompleteSignal.IsSet);
+ startWorkloadSignal.Reset();
+ workloadCompleteSignal.Reset();
CommandHelper.s_sleepDuringRunExecuteReaderTdsForSpDescribeParameterEncryption?.SetValue(null, true);
// Invoke ExecuteReader or ExecuteNonQuery in another thread.
- threads = new Thread[2];
+ tasks = new Task[2];
if (executeMethod == @"ExecuteReader")
{
- threads[0] = new Thread(new ParameterizedThreadStart(Thread_ExecuteReader));
+ tasks[0] = new Task(Thread_ExecuteReader,
+ new TestCommandCancelParams(sqlCommand, _tableName, startWorkloadSignal, workloadCompleteSignal),
+ TaskCreationOptions.LongRunning);
}
else
{
- threads[0] = new Thread(new ParameterizedThreadStart(Thread_ExecuteNonQuery));
+ tasks[0] = new Task(Thread_ExecuteNonQuery,
+ new TestCommandCancelParams(sqlCommand, _tableName, startWorkloadSignal, workloadCompleteSignal),
+ TaskCreationOptions.LongRunning);
}
- threads[1] = new Thread(new ParameterizedThreadStart(Thread_Cancel));
+ tasks[1] = new Task(Thread_Cancel,
+ new TestCommandCancelParams(sqlCommand, _tableName, startWorkloadSignal, workloadCompleteSignal),
+ TaskCreationOptions.LongRunning);
// Start the execute thread.
- threads[0].Start(new TestCommandCancelParams(sqlCommand, _tableName, numberOfCancelCalls));
+ tasks[0].Start();
// Start the thread which cancels the above command started by the execute thread.
- threads[1].Start(new TestCommandCancelParams(sqlCommand, _tableName, numberOfCancelCalls));
+ tasks[1].Start();
// Wait for the threads to finish.
- threads[0].Join();
- threads[1].Join();
+ Task.WaitAll(tasks);
CommandHelper.s_sleepDuringRunExecuteReaderTdsForSpDescribeParameterEncryption?.SetValue(null, false);
- // Verify the state of the sql command object.
+ // Verify the state of the sql command object. Also ensure that the exit lock was set (and didn't time out.)
VerifySqlCommandStateAfterCompletionOrCancel(sqlCommand);
+ Assert.True(workloadCompleteSignal.IsSet);
+ startWorkloadSignal.Reset();
+ workloadCompleteSignal.Reset();
rowsAffected = 0;
@@ -2113,31 +2162,37 @@ public void TestSqlCommandCancel(string connection, string value, int number)
CommandHelper.s_sleepAfterReadDescribeEncryptionParameterResults?.SetValue(null, true);
- threads = new Thread[2];
+ tasks = new Task[2];
if (executeMethod == @"ExecuteReader")
{
- threads[0] = new Thread(new ParameterizedThreadStart(Thread_ExecuteReader));
+ tasks[0] = new Task(Thread_ExecuteReader,
+ new TestCommandCancelParams(sqlCommand, _tableName, startWorkloadSignal, workloadCompleteSignal),
+ TaskCreationOptions.LongRunning);
}
else
{
- threads[0] = new Thread(new ParameterizedThreadStart(Thread_ExecuteNonQuery));
+ tasks[0] = new Task(Thread_ExecuteNonQuery,
+ new TestCommandCancelParams(sqlCommand, _tableName, startWorkloadSignal, workloadCompleteSignal),
+ TaskCreationOptions.LongRunning);
}
- threads[1] = new Thread(new ParameterizedThreadStart(Thread_Cancel));
+ tasks[1] = new Task(Thread_Cancel,
+ new TestCommandCancelParams(sqlCommand, _tableName, startWorkloadSignal, workloadCompleteSignal),
+ TaskCreationOptions.LongRunning);
// Start the execute thread.
- threads[0].Start(new TestCommandCancelParams(sqlCommand, _tableName, numberOfCancelCalls));
+ tasks[0].Start();
// Start the thread which cancels the above command started by the execute thread.
- threads[1].Start(new TestCommandCancelParams(sqlCommand, _tableName, numberOfCancelCalls));
+ tasks[1].Start();
// Wait for the threads to finish.
- threads[0].Join();
- threads[1].Join();
+ Task.WaitAll(tasks);
CommandHelper.s_sleepAfterReadDescribeEncryptionParameterResults?.SetValue(null, false);
- // Verify the state of the sql command object.
+ // Verify the state of the sql command object. Also ensure that the exit lock was set (and didn't time out.)
VerifySqlCommandStateAfterCompletionOrCancel(sqlCommand);
+ Assert.True(workloadCompleteSignal.IsSet);
rowsAffected = 0;
@@ -3130,47 +3185,97 @@ private void TestCancellationToken(FieldInfo failpoint, SqlCommand sqlCommand, i
Assert.True(rowsAffected == numberOfRows, "Unexpected number of rows affected as returned by EndExecuteReader.");
}
- private void Thread_ExecuteReader(object cancelCommandTestParamsObject)
+ private void Thread_ExecuteReader(object state)
{
+ TestCommandCancelParams cancelCommandTestParamsObject = state as TestCommandCancelParams;
+ SqlCommand sqlCommand = cancelCommandTestParamsObject?.SqlCommand;
+ SqlDataReader reader = null;
+
Assert.True(cancelCommandTestParamsObject != null, @"cancelCommandTestParamsObject should not be null.");
- SqlCommand sqlCommand = ((TestCommandCancelParams)cancelCommandTestParamsObject).SqlCommand as SqlCommand;
Assert.True(sqlCommand != null, "sqlCommand should not be null.");
- string.Format(@"SELECT * FROM {0} WHERE FirstName = @FirstName AND CustomerId = @CustomerId", ((TestCommandCancelParams)cancelCommandTestParamsObject).TableName);
- using (SqlDataReader reader = sqlCommand.ExecuteReader())
+ try
{
- while (reader.Read())
+ // Wait for the cancellation thread to open this lock...
+ cancelCommandTestParamsObject.StartWorkloadSignal.Wait();
+
+ Exception ex = Assert.ThrowsAny(() =>
{
- Assert.Throws(() => sqlCommand.ExecuteReader());
+ reader = sqlCommand.ExecuteReader();
+ while (reader.Read())
+ { }
+ });
+
+ // We don't use Assert.Contains() here because it truncates the
+ // actual and expected strings when outputting a failure.
+ if (!_cancellationExceptionMessages.Contains(ex.Message))
+ {
+ Assert.Fail(
+ $"Exception message \"{ex.Message}\" not found in: [\"" +
+ string.Join("\", \"", _cancellationExceptionMessages) + "\"]");
}
}
+ finally
+ {
+ reader?.Dispose();
+ // ...and unlock the cancellation thread once we finish.
+ cancelCommandTestParamsObject.WorkloadCompleteSignal.Set();
+ }
}
- private void Thread_ExecuteNonQuery(object cancelCommandTestParamsObject)
+ private void Thread_ExecuteNonQuery(object state)
{
+ TestCommandCancelParams cancelCommandTestParamsObject = state as TestCommandCancelParams;
+ SqlCommand sqlCommand = cancelCommandTestParamsObject?.SqlCommand;
+
Assert.True(cancelCommandTestParamsObject != null, @"cancelCommandTestParamsObject should not be null.");
- SqlCommand sqlCommand = ((TestCommandCancelParams)cancelCommandTestParamsObject).SqlCommand as SqlCommand;
Assert.True(sqlCommand != null, "sqlCommand should not be null.");
- string.Format(@"UPDATE {0} SET FirstName = @FirstName WHERE FirstName = @FirstName AND CustomerId = @CustomerId", ((TestCommandCancelParams)cancelCommandTestParamsObject).TableName);
+ try
+ {
+ // Wait for the cancellation thread to open this lock...
+ cancelCommandTestParamsObject.StartWorkloadSignal.Wait();
+
+ Exception ex = Assert.ThrowsAny(() => sqlCommand.ExecuteNonQuery());
- Exception ex = Assert.Throws(() => sqlCommand.ExecuteNonQuery());
- Assert.Equal(@"Operation cancelled by user.", ex.Message);
+ // We don't use Assert.Contains() here because it truncates the
+ // actual and expected strings when outputting a failure.
+ if (!_cancellationExceptionMessages.Contains(ex.Message))
+ {
+ Assert.Fail(
+ $"Exception message \"{ex.Message}\" not found in: [\"" +
+ string.Join("\", \"", _cancellationExceptionMessages) + "\"]");
+ }
+ }
+ finally
+ {
+ // ...and unlock the cancellation thread once we finish.
+ cancelCommandTestParamsObject.WorkloadCompleteSignal.Set();
+ }
}
- private void Thread_Cancel(object cancelCommandTestParamsObject)
+ private void Thread_Cancel(object state)
{
+ TestCommandCancelParams cancelCommandTestParamsObject = state as TestCommandCancelParams;
+ SqlCommand sqlCommand = cancelCommandTestParamsObject?.SqlCommand;
+ int cancellations = 0;
+ System.Diagnostics.Stopwatch cancellationStart = new System.Diagnostics.Stopwatch();
+
Assert.True(cancelCommandTestParamsObject != null, @"cancelCommandTestParamsObject should not be null.");
- SqlCommand sqlCommand = ((TestCommandCancelParams)cancelCommandTestParamsObject).SqlCommand as SqlCommand;
Assert.True(sqlCommand != null, "sqlCommand should not be null.");
- Thread.Sleep(millisecondsTimeout: 500);
+ cancellationStart.Start();
+ cancelCommandTestParamsObject.StartWorkloadSignal.Set();
- // Repeatedly cancel.
- for (int i = 0; i < ((TestCommandCancelParams)cancelCommandTestParamsObject).NumberofTimesToRunCancel; i++)
+ // Repeatedly cancel until the other thread signals that it's completed execution
+ // (or until the command timeout has passed.)
+ do
{
sqlCommand.Cancel();
- }
+ cancellations++;
+ } while (!cancelCommandTestParamsObject.WorkloadCompleteSignal.Wait(0)
+ && cancellationStart.ElapsedMilliseconds <= sqlCommand.CommandTimeout);
+ cancellationStart.Stop();
}
public void Dispose()
@@ -3298,67 +3403,42 @@ internal TestAsyncCallBackStateObject(SqlCommand sqlCommand, int expectedRowsAff
internal class TestCommandCancelParams
{
///
- /// SqlCommand object.
- ///
- private readonly object _sqlCommand;
-
- ///
- /// Name of the test/works as table name
- ///
- private readonly string _tableName;
-
- ///
- /// number of times to run cancel.
+ /// Return the SqlCommand object.
///
- private readonly int _numberofCancelCommands;
+ public SqlCommand SqlCommand { get; }
///
- /// Return the SqlCommand object.
+ /// Return the table name (usually equal to the name of the test.)
///
- public object SqlCommand
- {
- get
- {
- return _sqlCommand;
- }
- }
+ public string TableName { get; }
///
- /// Return the tablename.
+ /// Lock set by the thread cancelling the SqlCommand.
///
- public object TableName
- {
- get
- {
- return _tableName;
- }
- }
+ public ManualResetEventSlim StartWorkloadSignal { get; }
///
- /// Return the number of times to run cancel.
+ /// Lock set by the thread executing the SqlCommand.
///
- public int NumberofTimesToRunCancel
- {
- get
- {
- return _numberofCancelCommands;
- }
- }
+ public ManualResetEventSlim WorkloadCompleteSignal { get; }
///
/// Constructor.
///
///
///
- ///
- public TestCommandCancelParams(object sqlCommand, string tableName, int numberofTimesToCancel)
+ ///
+ ///
+ ///
+ public TestCommandCancelParams(SqlCommand sqlCommand, string tableName, ManualResetEventSlim startWorkloadSignal, ManualResetEventSlim workloadCompleteSignal)
{
Assert.True(sqlCommand != null, "sqlCommand should not be null.");
Assert.True(!string.IsNullOrWhiteSpace(tableName), "tableName should not be null or empty.");
- _sqlCommand = sqlCommand;
- _tableName = tableName;
- _numberofCancelCommands = numberofTimesToCancel;
+ SqlCommand = sqlCommand;
+ TableName = tableName;
+ StartWorkloadSignal = startWorkloadSignal;
+ WorkloadCompleteSignal = workloadCompleteSignal;
}
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs
index bdd48967b5..54a4b0c175 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs
@@ -54,7 +54,7 @@ public void TestRoundTripWithCspAndCertStoreProvider()
[MemberData(nameof(TestEncryptDecryptWithCsp_Data))]
public void TestEncryptDecryptWithCsp(string connectionString, string providerName, int providerType)
{
- string keyIdentifier = DataTestUtility.GetUniqueNameForSqlServer("CSP");
+ string keyIdentifier = DataTestUtility.GetLongName("CSP");
CspParameters namedCspParameters = new CspParameters(providerType, providerName, keyIdentifier);
using SQLSetupStrategyCspProvider sqlSetupStrategyCsp = new SQLSetupStrategyCspProvider(namedCspParameters);
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs
index 2465633a03..15679176ac 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs
@@ -54,7 +54,7 @@ public void NullEncryptionAlgorithm()
public void EmptyColumnEncryptionKey()
{
Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(_fixture.AkvKeyUrl, MasterKeyEncAlgo, new byte[] { }));
- Assert.Matches($@"Internal error. Empty columnEncryptionKey specified.", ex1.Message);
+ Assert.Matches($@"Internal error. Empty 'columnEncryptionKey' specified.", ex1.Message);
}
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))]
@@ -68,7 +68,7 @@ public void NullColumnEncryptionKey()
public void EmptyEncryptedColumnEncryptionKey()
{
Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, MasterKeyEncAlgo, new byte[] { }));
- Assert.Matches($@"Internal error. Empty encryptedColumnEncryptionKey specified", ex1.Message);
+ Assert.Matches($@"Internal error. Empty 'encryptedColumnEncryptionKey' specified", ex1.Message);
}
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))]
@@ -250,7 +250,7 @@ public void InvalidTrustedEndpoints(string[] trustedEndpoints)
SqlColumnEncryptionAzureKeyVaultProvider azureKeyProvider = new SqlColumnEncryptionAzureKeyVaultProvider(
new SqlClientCustomTokenCredential(), trustedEndpoints);
});
- Assert.Matches("One or more of the elements in trustedEndpoints are null or empty or consist of only whitespace.", ex.Message);
+ Assert.Matches("One or more of the elements in 'trustedEndpoints' are null or empty or consist of only whitespace.", ex.Message);
}
[InlineData(null)]
@@ -264,7 +264,7 @@ public void InvalidTrustedEndpoint(string trustedEndpoint)
SqlColumnEncryptionAzureKeyVaultProvider azureKeyProvider = new SqlColumnEncryptionAzureKeyVaultProvider(
new SqlClientCustomTokenCredential(), trustedEndpoint);
});
- Assert.Matches("One or more of the elements in trustedEndpoints are null or empty or consist of only whitespace.", ex.Message);
+ Assert.Matches("One or more of the elements in 'trustedEndpoints' are null or empty or consist of only whitespace.", ex.Message);
}
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlDataAdapterBatchUpdateTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlDataAdapterBatchUpdateTests.cs
new file mode 100644
index 0000000000..0622f4b9f6
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlDataAdapterBatchUpdateTests.cs
@@ -0,0 +1,255 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Data;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using Microsoft.Data.SqlClient;
+using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup;
+using Xunit;
+
+namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted
+{
+ public sealed class SqlDataAdapterBatchUpdateTests : IClassFixture, IDisposable
+ {
+ private readonly SQLSetupStrategy _fixture;
+ private readonly Dictionary tableNames = new();
+
+ public SqlDataAdapterBatchUpdateTests(SQLSetupStrategyCertStoreProvider context)
+ {
+ _fixture = context;
+
+ // Provide table names to mirror repo patterns.
+ // If your fixture already exposes specific names for BuyerSeller and procs, wire them here.
+ // Otherwise use literal names as below.
+ tableNames["BuyerSeller"] = "BuyerSeller";
+ tableNames["ProcInsertBuyerSeller"] = "InsertBuyerSeller";
+ tableNames["ProcUpdateBuyerSeller"] = "UpdateBuyerSeller";
+ }
+
+ // ---------- TESTS ----------
+
+ [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))]
+ [ClassData(typeof(AEConnectionStringProvider))]
+ public async Task AdapterUpdate_BatchSizeGreaterThanOne_Succeeds(string connectionString)
+ {
+ // Arrange
+ // Ensure baseline rows exist
+ TruncateTables("BuyerSeller", connectionString);
+ PopulateTable("BuyerSeller", new (int id, string s1, string s2)[] {
+ (1, "123-45-6789", "987-65-4321"),
+ (2, "234-56-7890", "876-54-3210"),
+ (3, "345-67-8901", "765-43-2109"),
+ (4, "456-78-9012", "654-32-1098"),
+ }, connectionString);
+
+ using var conn = new SqlConnection(GetOpenConnectionString(connectionString, encryptionEnabled: true));
+ await conn.OpenAsync();
+
+ using var adapter = CreateAdapter(conn, updateBatchSize: 10); // failure repro: > 1
+ var dataTable = BuildBuyerSellerDataTable();
+ LoadCurrentRowsIntoDataTable(dataTable, conn);
+
+ // Mutate values for update
+ MutateForUpdate(dataTable);
+
+ // Act - With batch updates (UpdateBatchSize > 1), this previously threw NullReferenceException due to null systemParams in batch RPC mode
+ var updated = await Task.Run(() => adapter.Update(dataTable));
+
+ // Assert
+ Assert.Equal(dataTable.Rows.Count, updated);
+
+ }
+
+ [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))]
+ [ClassData(typeof(AEConnectionStringProvider))]
+ public async Task AdapterUpdate_BatchSizeOne_Succeeds(string connectionString)
+ {
+ // Arrange
+ TruncateTables("BuyerSeller", connectionString);
+ PopulateTable("BuyerSeller", new (int id, string s1, string s2)[] {
+ (1, "123-45-6789", "987-65-4321"),
+ (2, "234-56-7890", "876-54-3210"),
+ (3, "345-67-8901", "765-43-2109"),
+ (4, "456-78-9012", "654-32-1098"),
+ }, connectionString);
+
+ using var conn = new SqlConnection(GetOpenConnectionString(connectionString, encryptionEnabled: true));
+ await conn.OpenAsync();
+
+ using var adapter = CreateAdapter(conn, updateBatchSize: 1); // success path
+ var dataTable = BuildBuyerSellerDataTable();
+ LoadCurrentRowsIntoDataTable(dataTable, conn);
+
+ MutateForUpdate(dataTable);
+
+ // Act (should not throw)
+ var updatedRows = await Task.Run(() => adapter.Update(dataTable));
+
+ // Assert
+ Assert.Equal(dataTable.Rows.Count, updatedRows);
+
+ }
+
+ // ---------- HELPERS ----------
+
+ private SqlDataAdapter CreateAdapter(SqlConnection connection, int updateBatchSize)
+ {
+ // Insert
+ var insertCmd = new SqlCommand(tableNames["ProcInsertBuyerSeller"], connection)
+ {
+ CommandType = CommandType.StoredProcedure
+ };
+ insertCmd.Parameters.AddRange(new[]
+ {
+ new SqlParameter("@BuyerSellerID", SqlDbType.Int) { SourceColumn = "BuyerSellerID" },
+ new SqlParameter("@SSN1", SqlDbType.VarChar, 255) { SourceColumn = "SSN1" },
+ new SqlParameter("@SSN2", SqlDbType.VarChar, 255) { SourceColumn = "SSN2" },
+ });
+ insertCmd.UpdatedRowSource = UpdateRowSource.None;
+
+ // Update
+ var updateCmd = new SqlCommand(tableNames["ProcUpdateBuyerSeller"], connection)
+ {
+ CommandType = CommandType.StoredProcedure
+ };
+ updateCmd.Parameters.AddRange(new[]
+ {
+ new SqlParameter("@BuyerSellerID", SqlDbType.Int) { SourceColumn = "BuyerSellerID" },
+ new SqlParameter("@SSN1", SqlDbType.VarChar, 255) { SourceColumn = "SSN1" },
+ new SqlParameter("@SSN2", SqlDbType.VarChar, 255) { SourceColumn = "SSN2" },
+ });
+ updateCmd.UpdatedRowSource = UpdateRowSource.None;
+
+ return new SqlDataAdapter
+ {
+ InsertCommand = insertCmd,
+ UpdateCommand = updateCmd,
+ UpdateBatchSize = updateBatchSize
+ };
+ }
+
+ private DataTable BuildBuyerSellerDataTable()
+ {
+ var dt = new DataTable(tableNames["BuyerSeller"]);
+ dt.Columns.AddRange(new[]
+ {
+ new DataColumn("BuyerSellerID", typeof(int)),
+ new DataColumn("SSN1", typeof(string)),
+ new DataColumn("SSN2", typeof(string)),
+ });
+ dt.PrimaryKey = new[] { dt.Columns["BuyerSellerID"] };
+ return dt;
+ }
+
+ private void LoadCurrentRowsIntoDataTable(DataTable dt, SqlConnection conn)
+ {
+ using var cmd = new SqlCommand($"SELECT BuyerSellerID, SSN1, SSN2 FROM [dbo].[{tableNames["BuyerSeller"]}] ORDER BY BuyerSellerID", conn);
+ using var reader = cmd.ExecuteReader();
+ while (reader.Read())
+ {
+ dt.Rows.Add(reader.GetInt32(0), reader.GetString(1), reader.GetString(2));
+ }
+ }
+
+ private void MutateForUpdate(DataTable dt)
+ {
+ int i = 0;
+ var fixedTime = new DateTime(2023, 01, 01, 12, 34, 56); // Use any fixed value
+ string timeStr = fixedTime.ToString("HHmm");
+ foreach (DataRow row in dt.Rows)
+ {
+ i++;
+ row["SSN1"] = $"{i:000}-11-{timeStr}";
+ row["SSN2"] = $"{i:000}-22-{timeStr}";
+ }
+ }
+
+ internal void TruncateTables(string tableName, string connectionString)
+ {
+ using var connection = new SqlConnection(GetOpenConnectionString(connectionString, encryptionEnabled: true));
+ connection.Open();
+ SilentRunCommand($@"TRUNCATE TABLE [dbo].[{tableNames[tableName]}]", connection);
+ }
+
+ internal void ExecuteQuery(SqlConnection connection, string commandText)
+ {
+ // Mirror AE-enabled command execution style used in repo tests
+ using var cmd = new SqlCommand(
+ commandText,
+ connection: connection,
+ transaction: null,
+ columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled);
+ cmd.ExecuteNonQuery();
+ }
+
+ internal void PopulateTable(string tableName, (int id, string s1, string s2)[] rows, string connectionString)
+ {
+ using var connection = new SqlConnection(GetOpenConnectionString(connectionString, encryptionEnabled: true));
+ connection.Open();
+
+ foreach (var (id, s1, s2) in rows)
+ {
+ using var cmd = new SqlCommand(
+ $@"INSERT INTO [dbo].[{tableNames[tableName]}] (BuyerSellerID, SSN1, SSN2) VALUES (@id, @s1, @s2)",
+ connection,
+ null,
+ SqlCommandColumnEncryptionSetting.Enabled);
+
+ cmd.Parameters.Add(new SqlParameter("@id", SqlDbType.Int) { Value = id });
+ cmd.Parameters.Add(new SqlParameter("@s1", SqlDbType.VarChar, 255) { Value = s1 });
+ cmd.Parameters.Add(new SqlParameter("@s2", SqlDbType.VarChar, 255) { Value = s2 });
+
+ cmd.ExecuteNonQuery();
+ }
+ }
+
+ public string GetOpenConnectionString(string baseConnectionString, bool encryptionEnabled)
+ {
+ var builder = new SqlConnectionStringBuilder(baseConnectionString)
+ {
+ // TrustServerCertificate can be set based on environment; mirror repo’s AE toggling idiom
+ ColumnEncryptionSetting = encryptionEnabled
+ ? SqlConnectionColumnEncryptionSetting.Enabled
+ : SqlConnectionColumnEncryptionSetting.Disabled
+ };
+ return builder.ToString();
+ }
+
+ internal void SilentRunCommand(string commandText, SqlConnection connection)
+ {
+ try
+ { ExecuteQuery(connection, commandText); }
+ catch (SqlException ex)
+ {
+ // Only swallow "object does not exist" (error 208), log others
+ bool onlyObjectNotExist = true;
+ foreach (SqlError err in ex.Errors)
+ {
+ if (err.Number != 208)
+ {
+ onlyObjectNotExist = false;
+ break;
+ }
+ }
+ if (!onlyObjectNotExist)
+ {
+ Console.WriteLine($"SilentRunCommand: Unexpected SqlException during cleanup: {ex}");
+ }
+ // Swallow all exceptions, but log unexpected ones
+ }
+ }
+
+ public void Dispose()
+ {
+ foreach (string connectionString in DataTestUtility.AEConnStringsSetup)
+ {
+ using var connection = new SqlConnection(GetOpenConnectionString(connectionString, encryptionEnabled: true));
+ connection.Open();
+ SilentRunCommand($"DELETE FROM [dbo].[{tableNames["BuyerSeller"]}]", connection);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs
index 33d548cd85..9c85c4fc67 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs
@@ -290,10 +290,8 @@ public IEnumerator GetEnumerator()
{
foreach (string connStrAE in DataTestUtility.AEConnStrings)
{
- yield return new object[] { connStrAE, @"ExecuteReader", 1 };
- yield return new object[] { connStrAE, @"ExecuteReader", 3 };
- yield return new object[] { connStrAE, @"ExecuteNonQuery", 1 };
- yield return new object[] { connStrAE, @"ExecuteNonQuery", 3 };
+ yield return new object[] { connStrAE, @"ExecuteReader" };
+ yield return new object[] { connStrAE, @"ExecuteNonQuery" };
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
index aacc723453..41a9bf5923 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
@@ -7,7 +7,6 @@
using System.Data;
using System.Data.SqlTypes;
using System.Diagnostics.Tracing;
-using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
@@ -18,6 +17,7 @@
using System.Security;
using System.Security.Principal;
using System.Text;
+using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
@@ -25,6 +25,7 @@
using Microsoft.Data.SqlClient.TestUtilities;
using Microsoft.Identity.Client;
using Xunit;
+using Xunit.Abstractions;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
{
@@ -92,12 +93,6 @@ public static class DataTestUtility
//SQL Server EngineEdition
private static string s_sqlServerEngineEdition;
- // JSON Column type
- public static readonly bool IsJsonSupported = false;
-
- // VECTOR column type
- public static readonly bool IsVectorSupported = false;
-
// Azure Synapse EngineEditionId == 6
// More could be read at https://learn.microsoft.com/en-us/sql/t-sql/functions/serverproperty-transact-sql?view=sql-server-ver16#propertyname
public static bool IsAzureSynapse
@@ -106,7 +101,7 @@ public static bool IsAzureSynapse
{
if (!string.IsNullOrEmpty(TCPConnectionString))
{
- s_sqlServerEngineEdition ??= GetSqlServerProperty(TCPConnectionString, "EngineEdition");
+ s_sqlServerEngineEdition ??= GetSqlServerProperty(TCPConnectionString, ServerProperty.EngineEdition);
}
_ = int.TryParse(s_sqlServerEngineEdition, out int engineEditon);
return engineEditon == 6;
@@ -117,7 +112,7 @@ public static bool TcpConnectionStringDoesNotUseAadAuth
{
get
{
- SqlConnectionStringBuilder builder = new (TCPConnectionString);
+ SqlConnectionStringBuilder builder = new(TCPConnectionString);
return builder.Authentication == SqlAuthenticationMethod.SqlPassword || builder.Authentication == SqlAuthenticationMethod.NotSpecified;
}
}
@@ -128,7 +123,7 @@ public static string SQLServerVersion
{
if (!string.IsNullOrEmpty(TCPConnectionString))
{
- s_sQLServerVersion ??= GetSqlServerProperty(TCPConnectionString, "ProductMajorVersion");
+ s_sQLServerVersion ??= GetSqlServerProperty(TCPConnectionString, ServerProperty.ProductMajorVersion);
}
return s_sQLServerVersion;
}
@@ -179,7 +174,6 @@ static DataTestUtility()
ManagedIdentitySupported = c.ManagedIdentitySupported;
IsManagedInstance = c.IsManagedInstance;
AliasName = c.AliasName;
- IsJsonSupported = c.IsJsonSupported;
#if NETFRAMEWORK
System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Tls12;
@@ -242,7 +236,9 @@ public static IEnumerable GetConnectionStrings(bool withEnclave)
yield return TCPConnectionString;
}
// Named Pipes are not supported on Unix platform and for Azure DB
- if (Environment.OSVersion.Platform != PlatformID.Unix && IsNotAzureServer() && !string.IsNullOrEmpty(NPConnectionString))
+ if (Environment.OSVersion.Platform != PlatformID.Unix &&
+ IsNotAzureServer() &&
+ !string.IsNullOrEmpty(NPConnectionString))
{
yield return NPConnectionString;
}
@@ -291,29 +287,99 @@ private static Task AcquireTokenAsync(string authorityURL, string userID
public static bool IsKerberosTest => !string.IsNullOrEmpty(KerberosDomainUser) && !string.IsNullOrEmpty(KerberosDomainPassword);
- public static string GetSqlServerProperty(string connectionString, string propertyName)
+ #nullable enable
+
+ ///
+ /// Returns the current test name as:
+ ///
+ /// ClassName.MethodName
+ ///
+ /// xUnit v2 doesn't provide access to a test context, so we use
+ /// reflection into the ITestOutputHelper to get the test name.
+ ///
+ ///
+ ///
+ /// The output helper instance for the currently running test.
+ ///
+ ///
+ /// The current test name.
+ public static string CurrentTestName(ITestOutputHelper outputHelper)
+ {
+ // Reflect our way to the ITest instance.
+ var type = outputHelper.GetType();
+ Assert.NotNull(type);
+ var testMember = type.GetField("test", BindingFlags.Instance | BindingFlags.NonPublic);
+ Assert.NotNull(testMember);
+ var test = testMember.GetValue(outputHelper) as ITest;
+ Assert.NotNull(test);
+
+ // The DisplayName is in the format:
+ //
+ // Namespace.ClassName.MethodName(args)
+ //
+ // We only want the ClassName.MethodName portion.
+ //
+ Match match = TestNameRegex.Match(test.DisplayName);
+ Assert.True(match.Success);
+ // There should be 2 groups: the overall match, and the capture
+ // group.
+ Assert.Equal(2, match.Groups.Count);
+
+ // The portion we want is in the capture group.
+ return match.Groups[1].Value;
+ }
+
+ private static readonly Regex TestNameRegex = new(
+ // Capture the ClassName.MethodName portion, which may terminate
+ // the name, or have (args...) appended.
+ @"\.((?:[^.]+)\.(?:[^.\(]+))(?:\(.*\))?$",
+ RegexOptions.Compiled);
+
+ ///
+ /// SQL Server properties we can query.
+ ///
+ /// GOTCHA: The enum member names must match the property names
+ /// queryable via T-SQL SERVERPROPERTY(). See:
+ ///
+ /// https://learn.microsoft.com/en-us/sql/t-sql/functions/serverproperty-transact-sql
+ ///
+ public enum ServerProperty
+ {
+ ProductMajorVersion,
+ EngineEdition
+ }
+
+ public static string GetSqlServerProperty(string connectionString, ServerProperty property)
{
- string propertyValue = string.Empty;
using SqlConnection conn = new(connectionString);
conn.Open();
- SqlCommand command = conn.CreateCommand();
- command.CommandText = $"SELECT SERVERProperty('{propertyName}')";
- SqlDataReader reader = command.ExecuteReader();
- if (reader.Read())
+ return GetSqlServerProperty(conn, property);
+ }
+
+ public static string GetSqlServerProperty(SqlConnection connection, ServerProperty property)
+ {
+ using SqlCommand command = connection.CreateCommand();
+ command.CommandText = $"SELECT SERVERProperty('{property}')";
+ using SqlDataReader reader = command.ExecuteReader();
+
+ Assert.True(reader.Read());
+
+ switch (property)
{
- switch (propertyName)
- {
- case "EngineEdition":
- propertyValue = reader.GetInt32(0).ToString();
- break;
- case "ProductMajorVersion":
- propertyValue = reader.GetString(0);
- break;
- }
+ case ServerProperty.EngineEdition:
+ // EngineEdition is returned as an int.
+ return reader.GetInt32(0).ToString();
+ case ServerProperty.ProductMajorVersion:
+ default:
+ // ProductMajorVersion is returned as a string.
+ //
+ // Assume any unknown property is also a string.
+ return reader.GetString(0);
}
- return propertyValue;
}
+ #nullable disable
+
public static bool GetSQLServerStatusOnTDS8(string connectionString)
{
bool isTDS8Supported = false;
@@ -404,6 +470,8 @@ public static bool IsEnclaveAzureDatabaseSetup()
public static bool IsNotAzureSynapse() => !IsAzureSynapse;
+ public static bool IsNotManagedInstance() => !IsManagedInstance;
+
// Synapse: UDT Test Database not compatible with Azure Synapse.
public static bool IsUdtTestDatabasePresent() => IsDatabasePresent(UdtTestDbName) && IsNotAzureSynapse();
@@ -451,6 +519,11 @@ public static bool IsAADAuthorityURLSetup()
return !string.IsNullOrEmpty(AADAuthorityURL);
}
+ public static bool IsAzureServer()
+ {
+ return AreConnStringsSetup() && Utils.IsAzureSqlServer(new SqlConnectionStringBuilder(TCPConnectionString).DataSource);
+ }
+
public static bool IsNotAzureServer()
{
return !AreConnStringsSetup() || !Utils.IsAzureSqlServer(new SqlConnectionStringBuilder(TCPConnectionString).DataSource);
@@ -558,48 +631,176 @@ public static bool DoesHostAddressContainBothIPv4AndIPv6()
}
}
+ // Generate a new GUID and return the characters from its 1st and 4th
+ // parts, as shown here:
+ //
+ // 7ff01cb8-88c7-11f0-b433-00155d7e531e
+ // ^^^^^^^^ ^^^^
+ //
+ // These 12 characters are concatenated together without any
+ // separators. These 2 parts typically comprise a timestamp and clock
+ // sequence, most likely to be unique for tests that generate names in
+ // quick succession.
+ private static string GetGuidParts()
+ {
+ var guid = Guid.NewGuid().ToString();
+ // GOTCHA: The slice operator is inclusive of the start index and
+ // exclusive of the end index!
+ return guid.Substring(0, 8) + guid.Substring(19, 4);
+ }
+
///
- /// Generate a unique name to use in Sql Server;
- /// some providers does not support names (Oracle supports up to 30).
+ /// Generate a short unique database object name, whose maximum length
+ /// is 30 characters, with the format:
+ ///
+ /// _
+ ///
+ /// The Prefix will be truncated to satisfy the overall maximum length.
+ ///
+ /// The GUID parts will be the characters from the 1st and 4th blocks
+ /// from a traditional string representation, as shown here:
+ ///
+ /// 7ff01cb8-88c7-11f0-b433-00155d7e531e
+ /// ^^^^^^^^ ^^^^
+ ///
+ /// These 2 parts typically comprise a timestamp and clock sequence,
+ /// most likely to be unique for tests that generate names in quick
+ /// succession. The 12 characters are concatenated together without any
+ /// separators.
///
- /// The name length will be no more then (16 + prefix.Length + escapeLeft.Length + escapeRight.Length).
- /// Name without brackets.
- /// Unique name by considering the Sql Server naming rules.
- public static string GetUniqueName(string prefix, bool withBracket = true)
- {
- string escapeLeft = withBracket ? "[" : string.Empty;
- string escapeRight = withBracket ? "]" : string.Empty;
- string uniqueName = string.Format("{0}{1}_{2}_{3}{4}",
- escapeLeft,
- prefix,
- DateTime.Now.Ticks.ToString("X", CultureInfo.InvariantCulture), // up to 8 characters
- Guid.NewGuid().ToString().Substring(0, 6), // take the first 6 characters only
- escapeRight);
- return uniqueName;
+ ///
+ ///
+ /// The prefix to use when generating the unique name, truncated to at
+ /// most 18 characters when withBracket is false, and 16 characters when
+ /// withBracket is true.
+ ///
+ /// This should not contain any characters that cannot be used in
+ /// database object names. See:
+ ///
+ /// https://learn.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-ver17#rules-for-regular-identifiers
+ ///
+ ///
+ ///
+ /// When true, the entire generated name will be enclosed in square
+ /// brackets, for example:
+ ///
+ /// [MyPrefix_7ff01cb811f0]
+ ///
+ ///
+ ///
+ /// A unique database object name, no more than 30 characters long.
+ ///
+ public static string GetShortName(string prefix, bool withBracket = true)
+ {
+ StringBuilder name = new(30);
+
+ if (withBracket)
+ {
+ name.Append('[');
+ }
+
+ int maxPrefixLength = withBracket ? 16 : 18;
+ if (prefix.Length > maxPrefixLength)
+ {
+ prefix = prefix.Substring(0, maxPrefixLength);
+ }
+
+ name.Append(prefix);
+ name.Append('_');
+ name.Append(GetGuidParts());
+
+ if (withBracket)
+ {
+ name.Append(']');
+ }
+
+ return name.ToString();
}
///
- /// Uses environment values `UserName` and `MachineName` in addition to the specified `prefix` and current date
- /// to generate a unique name to use in Sql Server;
- /// SQL Server supports long names (up to 128 characters), add extra info for troubleshooting.
+ /// Generate a long unique database object name, whose maximum length is
+ /// 96 characters, with the format:
+ ///
+ /// ___
+ ///
+ /// The Prefix will be truncated to satisfy the overall maximum length.
+ ///
+ /// The GUID Parts will be the characters from the 1st and 4th blocks
+ /// from a traditional string representation, as shown here:
+ ///
+ /// 7ff01cb8-88c7-11f0-b433-00155d7e531e
+ /// ^^^^^^^^ ^^^^
+ ///
+ /// These 2 parts typically comprise a timestamp and clock sequence,
+ /// most likely to be unique for tests that generate names in quick
+ /// succession. The 12 characters are concatenated together without any
+ /// separators.
+ ///
+ /// The UserName and MachineName are obtained from the Environment,
+ /// and will be truncated to satisfy the maximum overall length.
///
- /// Add the prefix to the generate string.
- /// Database name must be pass with brackets by default.
- /// Unique name by considering the Sql Server naming rules.
- public static string GetUniqueNameForSqlServer(string prefix, bool withBracket = true)
+ ///
+ ///
+ /// The prefix to use when generating the unique name, truncated to at
+ /// most 32 characters.
+ ///
+ /// This should not contain any characters that cannot be used in
+ /// database object names. See:
+ ///
+ /// https://learn.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-ver17#rules-for-regular-identifiers
+ ///
+ ///
+ ///
+ /// When true, the entire generated name will be enclosed in square
+ /// brackets, for example:
+ ///
+ /// [MyPrefix_7ff01cb811f0_test_user_ci_agent_machine_name]
+ ///
+ ///
+ ///
+ /// A unique database object name, no more than 96 characters long.
+ ///
+ public static string GetLongName(string prefix, bool withBracket = true)
{
- string extendedPrefix = string.Format(
- "{0}_{1}_{2}@{3}",
- prefix,
- Environment.UserName,
- Environment.MachineName,
- DateTime.Now.ToString("yyyy_MM_dd", CultureInfo.InvariantCulture));
- string name = GetUniqueName(extendedPrefix, withBracket);
- if (name.Length > 128)
+ StringBuilder name = new(96);
+
+ if (withBracket)
+ {
+ name.Append('[');
+ }
+
+ if (prefix.Length > 32)
{
- throw new ArgumentOutOfRangeException("the name is too long - SQL Server names are limited to 128");
+ prefix = prefix.Substring(0, 32);
}
- return name;
+
+ name.Append(prefix);
+ name.Append('_');
+ name.Append(GetGuidParts());
+ name.Append('_');
+
+ var suffix =
+ Environment.UserName + '_' +
+ Environment.MachineName;
+
+ int maxSuffixLength = 96 - name.Length;
+ if (withBracket)
+ {
+ --maxSuffixLength;
+ }
+ if (suffix.Length > maxSuffixLength)
+ {
+ suffix = suffix.Substring(0, maxSuffixLength);
+ }
+
+ name.Append(suffix);
+
+ if (withBracket)
+ {
+ name.Append(']');
+ }
+
+ return name.ToString();
}
public static void CreateTable(SqlConnection sqlConnection, string tableName, string createBody)
@@ -1090,65 +1291,102 @@ protected virtual void OnMatchingEventWritten(EventWrittenEventArgs eventData)
}
}
- public readonly ref struct XEventScope // : IDisposable
+ #nullable enable
+
+ public readonly ref struct XEventScope // IDisposable
{
+ #region Private Fields
+
+ // Maximum dispatch latency for XEvents, in seconds.
+ private const int MaxDispatchLatencySeconds = 5;
+
+ // The connection to use for all operations.
private readonly SqlConnection _connection;
- private readonly bool _useDatabaseSession;
- public string SessionName { get; }
+ // True if connected to an Azure SQL instance.
+ private readonly bool _isAzureSql;
- public XEventScope(SqlConnection connection, string eventSpecification, string targetSpecification)
- {
- SessionName = GenerateRandomCharacters("Session");
- _connection = connection;
- // SQL Azure only supports database-scoped XEvent sessions
- _useDatabaseSession = GetSqlServerProperty(connection.ConnectionString, "EngineEdition") == "5";
+ // True if connected to a non-Azure SQL Server 2025 (version 17) or
+ // higher.
+ private readonly bool _isVersion17OrHigher;
- SetupXEvent(eventSpecification, targetSpecification);
- }
+ // Duration for the XEvent session, in minutes.
+ private readonly ushort _durationInMinutes;
- public void Dispose()
- => DropXEvent();
+ #endregion
- public System.Xml.XmlDocument GetEvents()
- {
- string xEventQuery = _useDatabaseSession
- ? $@"SELECT xet.target_data
- FROM sys.dm_xe_database_session_targets AS xet
- INNER JOIN sys.dm_xe_database_sessions AS xe
- ON (xe.address = xet.event_session_address)
- WHERE xe.name = '{SessionName}'"
- : $@"SELECT xet.target_data
- FROM sys.dm_xe_session_targets AS xet
- INNER JOIN sys.dm_xe_sessions AS xe
- ON (xe.address = xet.event_session_address)
- WHERE xe.name = '{SessionName}'";
+ #region Properties
- using (SqlCommand command = new SqlCommand(xEventQuery, _connection))
- {
- if (_connection.State == ConnectionState.Closed)
- {
- _connection.Open();
- }
+ ///
+ /// The name of the XEvent session, derived from the session name
+ /// provided at construction time, with a unique suffix appended.
+ ///
+ public string SessionName { get; }
+
+ #endregion
+
+ #region Construction
+
+ ///
+ /// Construct with the specified parameters.
+ ///
+ /// This will use the connection to query the server properties and
+ /// setup and start the XEvent session.
+ ///
+ /// The base name of the session.
+ /// The SQL connection to use. (Must already be open.)
+ /// The event specification T-SQL string.
+ /// The target specification T-SQL string.
+ /// The duration of the session in minutes.
+ public XEventScope(
+ string sessionName,
+ // The connection must already be open.
+ SqlConnection connection,
+ string eventSpecification,
+ string targetSpecification,
+ ushort durationInMinutes = 5)
+ {
+ SessionName = GenerateRandomCharacters(sessionName);
+
+ _connection = connection;
+ Assert.Equal(ConnectionState.Open, _connection.State);
+
+ _durationInMinutes = durationInMinutes;
- string targetData = command.ExecuteScalar() as string;
- System.Xml.XmlDocument xmlDocument = new System.Xml.XmlDocument();
+ // EngineEdition 5 indicates Azure SQL.
+ _isAzureSql = GetSqlServerProperty(connection, ServerProperty.EngineEdition) == "5";
- xmlDocument.LoadXml(targetData);
- return xmlDocument;
+ // Determine if we're connected to a SQL Server instance version
+ // 17 or higher.
+ if (!_isAzureSql)
+ {
+ int majorVersion;
+ Assert.True(
+ int.TryParse(
+ GetSqlServerProperty(connection, ServerProperty.ProductMajorVersion),
+ out majorVersion));
+ _isVersion17OrHigher = majorVersion >= 17;
}
- }
- private void SetupXEvent(string eventSpecification, string targetSpecification)
- {
- string sessionLocation = _useDatabaseSession ? "DATABASE" : "SERVER";
- string xEventCreateAndStartCommandText = $@"CREATE EVENT SESSION [{SessionName}] ON {sessionLocation}
+ // Setup and start the XEvent session.
+ string sessionLocation = _isAzureSql ? "DATABASE" : "SERVER";
+
+ // Both Azure SQL and SQL Server 2025+ support setting a maximum
+ // duration for the XEvent session.
+ string duration =
+ _isAzureSql || _isVersion17OrHigher
+ ? $"MAX_DURATION={_durationInMinutes} MINUTES,"
+ : string.Empty;
+
+ string xEventCreateAndStartCommandText =
+ $@"CREATE EVENT SESSION [{SessionName}] ON {sessionLocation}
{eventSpecification}
{targetSpecification}
WITH (
- MAX_MEMORY=4096 KB,
+ {duration}
+ MAX_MEMORY=16 MB,
EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,
- MAX_DISPATCH_LATENCY=30 SECONDS,
+ MAX_DISPATCH_LATENCY={MaxDispatchLatencySeconds} SECONDS,
MAX_EVENT_SIZE=0 KB,
MEMORY_PARTITION_MODE=NONE,
TRACK_CAUSALITY=ON,
@@ -1156,30 +1394,88 @@ private void SetupXEvent(string eventSpecification, string targetSpecification)
ALTER EVENT SESSION [{SessionName}] ON {sessionLocation} STATE = START ";
- using (SqlCommand createXEventSession = new SqlCommand(xEventCreateAndStartCommandText, _connection))
- {
- if (_connection.State == ConnectionState.Closed)
- {
- _connection.Open();
- }
+ using SqlCommand createXEventSession = new SqlCommand(xEventCreateAndStartCommandText, _connection);
+ createXEventSession.ExecuteNonQuery();
+ }
+
+ ///
+ /// Disposal stops and drops the XEvent session.
+ ///
+ ///
+ /// Disposal isn't perfect - tests can abort without cleaning up the
+ /// events they have created. For Azure SQL targets that outlive the
+ /// test pipelines, it is beneficial to periodically log into the
+ /// database and drop old XEvent sessions using T-SQL similar to
+ /// this:
+ ///
+ /// DECLARE @sql NVARCHAR(MAX) = N'';
+ ///
+ /// -- Identify inactive (stopped) event sessions and generate DROP commands
+ /// SELECT @sql += N'DROP EVENT SESSION [' + name + N'] ON SERVER;' + CHAR(13) + CHAR(10)
+ /// FROM sys.server_event_sessions
+ /// WHERE running = 0; -- Filter for sessions that are not running (inactive)
+ ///
+ /// -- Print the generated commands for review (optional, but recommended)
+ /// PRINT @sql;
+ ///
+ /// -- Execute the generated commands
+ /// EXEC sys.sp_executesql @sql;
+ ///
+ public void Dispose()
+ {
+ string dropXEventSessionCommand = _isAzureSql
+ // We choose the sys.(database|server)_event_sessions views
+ // here to ensure we find sessions that may not be running.
+ ? $"IF EXISTS (select * from sys.database_event_sessions where name ='{SessionName}')" +
+ $" DROP EVENT SESSION [{SessionName}] ON DATABASE"
+ : $"IF EXISTS (select * from sys.server_event_sessions where name ='{SessionName}')" +
+ $" DROP EVENT SESSION [{SessionName}] ON SERVER";
- createXEventSession.ExecuteNonQuery();
- }
+ using SqlCommand command = new SqlCommand(dropXEventSessionCommand, _connection);
+ command.ExecuteNonQuery();
}
- private void DropXEvent()
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Query the XEvent session for its collected events, returning
+ /// them as an XML document.
+ ///
+ /// This always blocks the thread for MaxDispatchLatencySeconds to
+ /// ensure that all events have been flushed into the ring buffer.
+ ///
+ public System.Xml.XmlDocument GetEvents()
{
- string dropXEventSessionCommand = _useDatabaseSession
- ? $"IF EXISTS (select * from sys.dm_xe_database_sessions where name ='{SessionName}')" +
- $" DROP EVENT SESSION [{SessionName}] ON DATABASE"
- : $"IF EXISTS (select * from sys.dm_xe_sessions where name ='{SessionName}')" +
- $" DROP EVENT SESSION [{SessionName}] ON SERVER";
+ string xEventQuery = _isAzureSql
+ ? $@"SELECT xet.target_data
+ FROM sys.dm_xe_database_session_targets AS xet
+ INNER JOIN sys.dm_xe_database_sessions AS xe
+ ON (xe.address = xet.event_session_address)
+ WHERE xe.name = '{SessionName}'"
+ : $@"SELECT xet.target_data
+ FROM sys.dm_xe_session_targets AS xet
+ INNER JOIN sys.dm_xe_sessions AS xe
+ ON (xe.address = xet.event_session_address)
+ WHERE xe.name = '{SessionName}'";
- using (SqlCommand command = new SqlCommand(dropXEventSessionCommand, _connection))
- {
- command.ExecuteNonQuery();
- }
+ using SqlCommand command = new SqlCommand(xEventQuery, _connection);
+
+ // Wait for maximum dispatch latency to ensure all events
+ // have been flushed to the ring buffer.
+ Thread.Sleep(MaxDispatchLatencySeconds * 1000);
+
+ string? targetData = command.ExecuteScalar() as string;
+ Assert.NotNull(targetData);
+
+ System.Xml.XmlDocument xmlDocument = new System.Xml.XmlDocument();
+
+ xmlDocument.LoadXml(targetData);
+ return xmlDocument;
}
+
+ #endregion
}
///
@@ -1208,4 +1504,6 @@ public static string GetMachineFQDN(string hostname)
return fqdn.ToString();
}
}
+
+ #nullable disable
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
index 7bd503c5b9..43574100b7 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
@@ -12,6 +12,9 @@
$(BinFolder)$(Configuration).$(Platform).$(AssemblyName)
true
+
+
+
@@ -185,6 +188,9 @@
+
+
+
@@ -213,6 +219,9 @@
+
+
+
@@ -252,6 +261,7 @@
+
@@ -271,6 +281,7 @@
+
@@ -294,13 +305,7 @@
-
-
-
-
-
-
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/ReaderTest/ReaderTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/ReaderTest/ReaderTest.cs
index c99fe94807..50e2b9253c 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/ReaderTest/ReaderTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/ReaderTest/ReaderTest.cs
@@ -19,7 +19,7 @@ public static void TestMain()
{
string connectionString = DataTestUtility.TCPConnectionString;
- string tempTable = DataTestUtility.GetUniqueNameForSqlServer("table");
+ string tempTable = DataTestUtility.GetLongName("table");
DbProviderFactory provider = SqlClientFactory.Instance;
try
@@ -275,7 +275,7 @@ public static void TestMain()
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
public static void SqlDataReader_SqlBuffer_GetFieldValue()
{
- string tableName = DataTestUtility.GetUniqueNameForSqlServer("SqlBuffer_GetFieldValue");
+ string tableName = DataTestUtility.GetLongName("SqlBuffer_GetFieldValue");
DateTimeOffset dtoffset = DateTimeOffset.Now;
DateTime dt = DateTime.Now;
//Exclude the millisecond because of rounding at some points by SQL Server.
@@ -374,7 +374,7 @@ public static void SqlDataReader_SqlBuffer_GetFieldValue()
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
public static async Task SqlDataReader_SqlBuffer_GetFieldValue_Async()
{
- string tableName = DataTestUtility.GetUniqueNameForSqlServer("SqlBuffer_GetFieldValue_Async");
+ string tableName = DataTestUtility.GetLongName("SqlBuffer_GetFieldValue_Async");
DateTimeOffset dtoffset = DateTimeOffset.Now;
DateTime dt = DateTime.Now;
//Exclude the millisecond because of rounding at some points by SQL Server.
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs
index 439406dadb..552fbf3119 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs
@@ -54,7 +54,7 @@ public class AdapterTest
public AdapterTest()
{
// create random name for temp tables
- _tempTable = DataTestUtility.GetUniqueName("AdapterTest");
+ _tempTable = DataTestUtility.GetShortName("AdapterTest");
_tempTable = _tempTable.Replace('-', '_');
_randomGuid = Guid.NewGuid().ToString();
@@ -555,7 +555,7 @@ public void ParameterTest_AllTypes()
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
public void ParameterTest_InOut()
{
- string procName = DataTestUtility.GetUniqueName("P");
+ string procName = DataTestUtility.GetShortName("P");
// input, output
string spCreateInOut =
"CREATE PROCEDURE " + procName + " @in int, @inout int OUTPUT, @out nvarchar(8) OUTPUT " +
@@ -836,13 +836,13 @@ public void BulkUpdateTest()
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
public void UpdateRefreshTest()
{
- string identTableName = DataTestUtility.GetUniqueName("ID_");
+ string identTableName = DataTestUtility.GetShortName("ID_");
string createIdentTable =
$"CREATE TABLE {identTableName} (id int IDENTITY," +
"LastName nvarchar(50) NULL," +
"Firstname nvarchar(50) NULL)";
- string spName = DataTestUtility.GetUniqueName("sp_insert", withBracket: false);
+ string spName = DataTestUtility.GetShortName("sp_insert", withBracket: false);
string spCreateInsert =
$"CREATE PROCEDURE {spName}" +
"(@FirstName nvarchar(50), @LastName nvarchar(50), @id int OUTPUT) " +
@@ -1155,7 +1155,7 @@ public void AutoGenUpdateTest()
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
public void AutoGenErrorTest()
{
- string identTableName = DataTestUtility.GetUniqueName("ID_");
+ string identTableName = DataTestUtility.GetShortName("ID_");
string createIdentTable =
$"CREATE TABLE {identTableName} (id int IDENTITY," +
"LastName nvarchar(50) NULL," +
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncCancelledConnectionsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncCancelledConnectionsTest.cs
index 0ae12be917..e71d6d62f6 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncCancelledConnectionsTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncCancelledConnectionsTest.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
@@ -37,13 +36,9 @@ public void CancelAsyncConnections()
private void RunCancelAsyncConnections(SqlConnectionStringBuilder connectionStringBuilder)
{
SqlConnection.ClearAllPools();
-
- ParallelLoopResult results = new ParallelLoopResult();
- ConcurrentDictionary tracker = new ConcurrentDictionary();
-
- _random = new Random(4); // chosen via fair dice roll.
_watch = Stopwatch.StartNew();
-
+ _random = new Random(4); // chosen via fair dice role.
+ ParallelLoopResult results = new ParallelLoopResult();
try
{
// Setup a timer so that we can see what is going on while our tasks run
@@ -52,7 +47,7 @@ private void RunCancelAsyncConnections(SqlConnectionStringBuilder connectionStri
results = Parallel.For(
fromInclusive: 0,
toExclusive: NumberOfTasks,
- (int i) => DoManyAsync(i, tracker, connectionStringBuilder).GetAwaiter().GetResult());
+ (int i) => DoManyAsync(connectionStringBuilder).GetAwaiter().GetResult());
}
}
catch (Exception ex)
@@ -87,15 +82,15 @@ private void DisplaySummary()
{
count = _exceptionDetails.Count;
}
+
_output.WriteLine($"{_watch.Elapsed} {_continue} Started:{_start} Done:{_done} InFlight:{_inFlight} RowsRead:{_rowsRead} ResultRead:{_resultRead} PoisonedEnded:{_poisonedEnded} nonPoisonedExceptions:{_nonPoisonedExceptions} PoisonedCleanupExceptions:{_poisonCleanUpExceptions} Count:{count} Found:{_found}");
}
// This is the the main body that our Tasks run
- private async Task DoManyAsync(int index, ConcurrentDictionary tracker, SqlConnectionStringBuilder connectionStringBuilder)
+ private async Task DoManyAsync(SqlConnectionStringBuilder connectionStringBuilder)
{
Interlocked.Increment(ref _start);
Interlocked.Increment(ref _inFlight);
- tracker[index] = true;
using (SqlConnection marsConnection = new SqlConnection(connectionStringBuilder.ToString()))
{
@@ -105,15 +100,15 @@ private async Task DoManyAsync(int index, ConcurrentDictionary tracker
}
// First poison
- await DoOneAsync(marsConnection, connectionStringBuilder.ToString(), poison: true, index);
+ await DoOneAsync(marsConnection, connectionStringBuilder.ToString(), poison: true);
for (int i = 0; i < NumberOfNonPoisoned && _continue; i++)
{
// now run some without poisoning
- await DoOneAsync(marsConnection, connectionStringBuilder.ToString(),false,index);
+ await DoOneAsync(marsConnection, connectionStringBuilder.ToString());
}
}
- tracker.TryRemove(index, out var _);
+
Interlocked.Decrement(ref _inFlight);
Interlocked.Increment(ref _done);
}
@@ -122,7 +117,7 @@ private async Task DoManyAsync(int index, ConcurrentDictionary tracker
// if we are poisoning we will
// 1 - Interject some sleeps in the sql statement so that it will run long enough that we can cancel it
// 2 - Setup a time bomb task that will cancel the command a random amount of time later
- private async Task DoOneAsync(SqlConnection marsConnection, string connectionString, bool poison, int parent)
+ private async Task DoOneAsync(SqlConnection marsConnection, string connectionString, bool poison = false)
{
try
{
@@ -140,12 +135,12 @@ private async Task DoOneAsync(SqlConnection marsConnection, string connectionStr
{
if (marsConnection != null && marsConnection.State == System.Data.ConnectionState.Open)
{
- await RunCommand(marsConnection, builder.ToString(), poison, parent);
+ await RunCommand(marsConnection, builder.ToString(), poison);
}
else
{
await connection.OpenAsync();
- await RunCommand(connection, builder.ToString(), poison, parent);
+ await RunCommand(connection, builder.ToString(), poison);
}
}
}
@@ -181,7 +176,7 @@ private async Task DoOneAsync(SqlConnection marsConnection, string connectionStr
}
}
- private async Task RunCommand(SqlConnection connection, string commandText, bool poison, int parent)
+ private async Task RunCommand(SqlConnection connection, string commandText, bool poison)
{
int rowsRead = 0;
int resultRead = 0;
@@ -216,7 +211,7 @@ private async Task RunCommand(SqlConnection connection, string commandText, bool
}
while (await reader.NextResultAsync() && _continue);
}
- catch (SqlException) when (poison)
+ catch when (poison)
{
// This looks a little strange, we failed to read above so this should fail too
// But consider the case where this code is elsewhere (in the Dispose method of a class holding this logic)
@@ -233,10 +228,6 @@ private async Task RunCommand(SqlConnection connection, string commandText, bool
throw;
}
- catch (Exception ex)
- {
- Assert.Fail("unexpected exception: " + ex.GetType().Name + " " +ex.Message);
- }
}
}
finally
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserStateObjectHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserStateObjectHelper.cs
index dadbb5c58c..cd20701f74 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserStateObjectHelper.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserStateObjectHelper.cs
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
-using System.Diagnostics;
using System.Reflection;
using Xunit;
@@ -97,14 +96,17 @@ internal static object GetSessionHandle(object stateObject)
{
throw new ArgumentNullException(nameof(stateObject));
}
+
if (s_tdsParserStateObjectManaged is null)
{
throw new ArgumentException("Library being tested does not implement TdsParserStateObjectManaged", nameof(stateObject));
}
+
if (!s_tdsParserStateObjectManaged.IsInstanceOfType(stateObject))
{
throw new ArgumentException("Object provided was not a TdsParserStateObjectManaged", nameof(stateObject));
}
+
return s_tdsParserStateObjectManagedSessionHandle.GetValue(stateObject);
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolStressTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolStressTest.cs
new file mode 100644
index 0000000000..d3d41d4c9a
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolStressTest.cs
@@ -0,0 +1,440 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+#nullable enable
+
+namespace Microsoft.Data.SqlClient.ManualTesting.Tests
+{
+ ///
+ /// Connection pool stress test to validate pool behavior under various concurrent load scenarios.
+ ///
+ public class ConnectionPoolStressTest
+ {
+ #region Properties
+
+ ///
+ /// Connection string
+ ///
+ internal string? ConnectionString { get; set; }
+
+ ///
+ /// Maximum number of connections in the pool
+ ///
+ public int MaxPoolSize { get; set; } = 100;
+
+ ///
+ /// SQL WAITFOR DELAY value for simulating slow queries
+ ///
+ public string WaitForDelay { get; set; } = "00:00:00.100";
+
+ ///
+ /// Number of concurrent connections to create
+ ///
+ public int ConcurrentConnections { get; set; } = 10;
+
+ ///
+ /// Number of operations each thread should perform
+ ///
+ public int OperationsPerThread { get; set; } = 10;
+
+ #endregion
+
+ #region Connection Dooming
+
+ // Reflection fields for accessing internal connection properties
+ private readonly FieldInfo? _internalConnectionField;
+
+ public ConnectionPoolStressTest()
+ {
+ try
+ {
+ // Cache reflection info for Microsoft.Data.SqlClient
+ Type msDataConnectionType = typeof(SqlConnection);
+ _internalConnectionField = msDataConnectionType.GetField("_innerConnection", BindingFlags.NonPublic | BindingFlags.Instance);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Warning: Failed to initialize reflection for connection dooming: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Dooms a Microsoft.Data.SqlClient connection by calling DoomThisConnection on its internal connection
+ ///
+ private bool DoomMicrosoftDataConnection(SqlConnection connection)
+ {
+ try
+ {
+ if(_internalConnectionField == null)
+ {
+ // Fail the test if reflection setup failed
+ return false;
+ }
+
+ if (_internalConnectionField.GetValue(connection) is object internalConnection)
+ {
+ MethodInfo? doomMethod = internalConnection.GetType().GetMethod("DoomThisConnection", BindingFlags.NonPublic | BindingFlags.Instance);
+ if (doomMethod != null)
+ {
+ doomMethod.Invoke(internalConnection, null);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
+ #endregion
+
+ #region Configuration
+
+ ///
+ /// Sets up connection string
+ ///
+ /// Connection string to be set.
+ internal void SetConnectionString(string connectionString)
+ {
+ var connectionSB = new SqlConnectionStringBuilder(connectionString)
+ {
+ // Min size needs to be larger than the number of concurrent connections to trigger the pool exhaustion as it will make it more likely that PoolCreateRequest will run.
+ MinPoolSize = Math.Min(20, MaxPoolSize / 5), // Dynamic min pool size
+ MaxPoolSize = MaxPoolSize,
+ Pooling = true, // Explicitly enable pooling
+ TrustServerCertificate = true
+ };
+
+ ConnectionString = connectionSB.ConnectionString;
+
+ // Ensure adequate thread pool capacity
+ ThreadPool.SetMaxThreads(Math.Max(ConcurrentConnections * 2, 100), 100);
+ }
+
+ #endregion
+
+ #region Stress Test Methods
+
+ ///
+ /// Runs a synchronous stress test using Microsoft.Data.SqlClient with connection dooming
+ ///
+ internal void ConnectionPoolStress_MsData_Sync()
+ {
+ if (ConnectionString == null)
+ {
+ throw new InvalidOperationException("ConnectionString is not set. Call SetConnectionString() before running the test.");
+ }
+
+ RunStressTest(
+ connectionString: ConnectionString,
+ doomAction: conn => DoomMicrosoftDataConnection((SqlConnection)conn),
+ async: false
+ );
+ }
+
+ ///
+ /// Runs asynchronous stress test using Microsoft.Data.SqlClient with connection dooming
+ ///
+ internal void ConnectionPoolStress_MsData_Async()
+ {
+ if (ConnectionString == null)
+ {
+ throw new InvalidOperationException("ConnectionString is not set. Call SetConnectionString() before running the test.");
+ }
+
+ RunStressTest(
+ connectionString: ConnectionString,
+ doomAction: conn => DoomMicrosoftDataConnection((SqlConnection)conn),
+ async: true
+ );
+ }
+
+ ///
+ /// Generic stress test method that works with both SQL client libraries using DbConnection/DbCommand
+ ///
+ private void RunStressTest(
+ string connectionString,
+ Func doomAction,
+ bool async = false)
+ {
+ var threads = new Thread[ConcurrentConnections];
+ using Barrier barrier = new(ConcurrentConnections);
+ using CountdownEvent countdown = new(ConcurrentConnections);
+
+ var command = string.IsNullOrWhiteSpace(WaitForDelay)
+ ? "SELECT GETDATE()"
+ : $"WAITFOR DELAY '{WaitForDelay}'; SELECT GETDATE()";
+
+ // Create regular threads (don't doom connections)
+ for (int i = 0; i < ConcurrentConnections - 1; i++)
+ {
+ threads[i] = CreateWorkerThread(
+ connectionString, command, barrier, countdown, doomConnections: false, async);
+ }
+
+ // Create special thread that dooms connections (if we have multiple threads)
+ if (ConcurrentConnections > 1)
+ {
+ threads[ConcurrentConnections - 1] = CreateWorkerThread(
+ connectionString, command, barrier, countdown, doomConnections: true, async, doomAction);
+ }
+
+ // Start all threads
+ foreach (Thread thread in threads.Where(t => t != null))
+ {
+ thread.Start();
+ }
+
+ // Wait for completion
+ countdown.Wait();
+ }
+
+ ///
+ /// Creates a worker thread that performs database operations using DbConnection/DbCommand
+ ///
+ private Thread CreateWorkerThread(
+ string connectionString,
+ string command,
+ Barrier barrier,
+ CountdownEvent countdown,
+ bool doomConnections,
+ bool async,
+ Func? doomAction = null)
+ {
+ return new Thread(async () =>
+ {
+ try
+ {
+ barrier.SignalAndWait(); // Initial synchronization - all threads start together
+
+ for (int j = 0; j < OperationsPerThread; j++)
+ {
+ if (doomConnections && doomAction != null)
+ {
+ // Dooming thread - barriers inside using block to doom before disposal
+ using var conn = new SqlConnection(connectionString);
+ if (async)
+ {
+ await conn.OpenAsync();
+ }
+ else
+ {
+ conn.Open();
+ }
+
+ await ExecuteCommand(command, async, conn);
+
+ // Synchronize after command execution, before dooming
+ barrier.SignalAndWait();
+
+ // Doom connection before it gets disposed/returned to pool
+ if (!doomAction(conn))
+ {
+ throw new Exception("Unable to doom connection");
+ }
+
+ // Synchronize after dooming - ensures all threads see the effect
+ barrier.SignalAndWait();
+ }
+ else
+ {
+ // Non-dooming threads - barriers after connection is closed
+ using (var conn = new SqlConnection(connectionString))
+ {
+ if (async)
+ {
+ await conn.OpenAsync();
+ }
+ else
+ {
+ conn.Open();
+ }
+
+ await ExecuteCommand(command, async, conn);
+
+ } // Connection is closed/returned to pool here
+
+ // Synchronize after connection is closed
+ barrier.SignalAndWait();
+
+ // Sync for coordination with dooming thread
+ barrier.SignalAndWait();
+ }
+ }
+ }
+ finally
+ {
+ countdown.Signal();
+ }
+ })
+ {
+ IsBackground = true // Make threads background threads for cleaner shutdown
+ };
+ }
+
+ ///
+ /// Executes a database command with proper error handling
+ ///
+ private static async Task ExecuteCommand(string command, bool async, SqlConnection conn)
+ {
+ try
+ {
+ using var cmd = new SqlCommand(command, conn);
+ if (async)
+ {
+ await cmd.ExecuteScalarAsync();
+ }
+ else
+ {
+ cmd.ExecuteScalar();
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Command execution failed: {ex.Message}");
+ }
+ }
+
+ #endregion
+
+ #region Helpers
+
+ private static bool RunSingleStressTest(Action testAction)
+ {
+ try
+ {
+ var stopwatch = Stopwatch.StartNew();
+ testAction();
+ stopwatch.Stop();
+ }
+ catch (Exception ex)
+ {
+ if (ex.InnerException != null)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static async Task TestConnectionPoolExhaustion(string connectionString, int maxPoolSize, bool async)
+ {
+ var connections = new List();
+
+ try
+ {
+ for (int i = 0; i < maxPoolSize; i++)
+ {
+ SqlConnection conn = new(connectionString);
+ if (async)
+ {
+ await conn.OpenAsync();
+ }
+ else
+ {
+ conn.Open();
+ }
+ connections.Add(conn);
+ }
+ Assert.Equal(maxPoolSize, connections.Count);
+ }
+ catch
+ {
+ return false;
+ }
+ finally
+ {
+ // Clean up all connections
+ foreach (SqlConnection conn in connections)
+ {
+ conn?.Dispose();
+ }
+ }
+
+ return true;
+ }
+
+ #endregion
+
+ #region Pool Exhaustion Tests
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ [TestCategory("LongRunning")] // Takes around 13 seconds.
+ public async Task ConnectionPoolStress_Sync()
+ {
+ var test = new ConnectionPoolStressTest
+ {
+ MaxPoolSize = 100,
+ ConcurrentConnections = 10,
+ WaitForDelay = "00:00:00.100",
+ OperationsPerThread = 100,
+ };
+
+ test.SetConnectionString(DataTestUtility.TCPConnectionString);
+
+ // Run the stress tests
+ if (!RunSingleStressTest(test.ConnectionPoolStress_MsData_Sync))
+ {
+ // fail the test
+ Assert.Fail("ConnectionPoolStress_MsData_Sync failed");
+ }
+
+ if (!await TestConnectionPoolExhaustion(test.ConnectionString!, test.MaxPoolSize, false))
+ {
+ // fail the test
+ Assert.Fail("ConnectionPoolStress_MsData_Sync failed");
+ }
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ [TestCategory("LongRunning")] // Takes around 11 seconds.
+ public async Task ConnectionPoolStress_Async()
+ {
+ var test = new ConnectionPoolStressTest
+ {
+ MaxPoolSize = 100,
+ ConcurrentConnections = 10,
+ WaitForDelay = "00:00:00.100",
+ OperationsPerThread = 100,
+ };
+
+ test.SetConnectionString(DataTestUtility.TCPConnectionString);
+
+ // Test Microsoft.Data.SqlClient Async
+ if (!RunSingleStressTest(test.ConnectionPoolStress_MsData_Async))
+ {
+ // fail the test
+ Assert.Fail("ConnectionPoolStress_MsData_Async failed");
+ }
+
+ // Test connection pool exhaustion (async)
+ if (!await TestConnectionPoolExhaustion(test.ConnectionString!, test.MaxPoolSize, true))
+ {
+ // fail the test
+ Assert.Fail("ConnectionPoolStress_MsData_Async failed");
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTestWithTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTestWithTdsServer.cs
index 48b0c9273e..169f71704a 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTestWithTdsServer.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTestWithTdsServer.cs
@@ -12,8 +12,10 @@
using System.ServiceProcess;
using System.Text;
using Microsoft.Data.SqlClient.ManualTesting.Tests.DataCommon;
+using Microsoft.SqlServer.TDS.Servers;
using Microsoft.Win32;
using Xunit;
+#nullable enable
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
{
@@ -129,18 +131,19 @@ private void ConnectionTest(ConnectionTestParameters connectionTestParameters)
string userId = string.IsNullOrWhiteSpace(builder.UserID) ? "user" : builder.UserID;
string password = string.IsNullOrWhiteSpace(builder.Password) ? "password" : builder.Password;
- using TestTdsServer server = TestTdsServer.StartTestServer(enableFedAuth: false, enableLog: false, connectionTimeout: 15,
- methodName: "",
-#if NET9_0_OR_GREATER
- X509CertificateLoader.LoadPkcs12FromFile(s_fullPathToPfx, "nopassword", X509KeyStorageFlags.UserKeySet),
-#else
- new X509Certificate2(s_fullPathToPfx, "nopassword", X509KeyStorageFlags.UserKeySet),
-#endif
- encryptionProtocols: connectionTestParameters.EncryptionProtocols,
- encryptionType: connectionTestParameters.TdsEncryptionType);
+ using TdsServer server = new TdsServer(new TdsServerArguments
+ {
+ EncryptionCertificate = GetEncryptionCertificate(s_fullPathToPfx, "nopassword", X509KeyStorageFlags.UserKeySet),
+ EncryptionProtocols = connectionTestParameters.EncryptionProtocols,
+ Encryption = connectionTestParameters.TdsEncryptionType,
+ });
+
+ server.Start();
- builder = new(server.ConnectionString)
+ builder = new()
{
+ DataSource = $"localhost,{server.EndPoint.Port}",
+ ConnectTimeout = 15,
UserID = userId,
Password = password,
TrustServerCertificate = connectionTestParameters.TrustServerCertificate,
@@ -231,6 +234,22 @@ private static void RunPowershellScript(string script)
}
}
+ ///
+ /// Loads the specified certificate.
+ ///
+ /// The full path of the certificate.
+ /// The certificate's password.
+ /// Key storage flags to apply when loading the certificate
+ /// An instance.
+ private X509Certificate2 GetEncryptionCertificate(string fileName, string? password, X509KeyStorageFlags keyStorageFlags)
+ {
+#if NET9_0_OR_GREATER
+ return X509CertificateLoader.LoadPkcs12FromFile(fileName, password, keyStorageFlags);
+#else
+ return new X509Certificate2(fileName, password, keyStorageFlags);
+#endif
+ }
+
private void RemoveCertificate()
{
string thumbprint = File.ReadAllText(s_fullPathTothumbprint);
@@ -249,7 +268,7 @@ private void RemoveCertificate()
private static void RemoveForceEncryptionFromRegistryPath(string registryPath)
{
- RegistryKey key = Registry.LocalMachine.OpenSubKey(registryPath, true);
+ RegistryKey? key = Registry.LocalMachine.OpenSubKey(registryPath, true);
key?.SetValue("ForceEncryption", 0, RegistryValueKind.DWord);
key?.SetValue("Certificate", "", RegistryValueKind.String);
ServiceController sc = new($"{s_instanceNamePrefix}{s_instanceName}");
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs
index e9e29e5940..8e21156bce 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs
@@ -171,7 +171,7 @@ public class ConnectionWorker : IDisposable
private static List s_workerList = new();
private ManualResetEventSlim _doneEvent = new(false);
private double _timeElapsed;
- private Thread _thread;
+ private Task _task;
private string _connectionString;
private int _numOfTry;
@@ -180,7 +180,7 @@ public ConnectionWorker(string connectionString, int numOfTry)
s_workerList.Add(this);
_connectionString = connectionString;
_numOfTry = numOfTry;
- _thread = new Thread(new ThreadStart(SqlConnectionOpen));
+ _task = new Task(SqlConnectionOpen, TaskCreationOptions.LongRunning);
}
public static List WorkerList => s_workerList;
@@ -191,7 +191,7 @@ public static void Start()
{
foreach (ConnectionWorker w in s_workerList)
{
- w._thread.Start();
+ w._task.Start();
}
}
@@ -393,7 +393,7 @@ public static async Task ConnectionOpenAsyncDisableRetry()
{
SqlConnectionStringBuilder connectionStringBuilder = new(DataTestUtility.TCPConnectionString)
{
- InitialCatalog = DataTestUtility.GetUniqueNameForSqlServer("DoesNotExist", false),
+ InitialCatalog = DataTestUtility.GetLongName("DoesNotExist", false),
Pooling = false,
ConnectTimeout = 15,
ConnectRetryCount = 3
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataClassificationTest/DataClassificationTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataClassificationTest/DataClassificationTest.cs
index 83eecc5b23..3e7076d52d 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataClassificationTest/DataClassificationTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataClassificationTest/DataClassificationTest.cs
@@ -18,7 +18,7 @@ public static class DataClassificationTest
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsSupportedDataClassification))]
public static void TestDataClassificationResultSetRank()
{
- s_tableName = DataTestUtility.GetUniqueNameForSqlServer("DC");
+ s_tableName = DataTestUtility.GetLongName("DC");
using (SqlConnection sqlConnection = new SqlConnection(DataTestUtility.TCPConnectionString))
using (SqlCommand sqlCommand = sqlConnection.CreateCommand())
{
@@ -41,7 +41,7 @@ public static void TestDataClassificationResultSetRank()
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSupportedDataClassification))]
public static void TestDataClassificationResultSet()
{
- s_tableName = DataTestUtility.GetUniqueNameForSqlServer("DC");
+ s_tableName = DataTestUtility.GetLongName("DC");
using (SqlConnection sqlConnection = new SqlConnection(DataTestUtility.TCPConnectionString))
using (SqlCommand sqlCommand = sqlConnection.CreateCommand())
{
@@ -232,7 +232,7 @@ public static void TestDataClassificationBulkCopy()
data.Rows.Add(Guid.NewGuid(), "Company 2", "sample2@contoso.com", 1);
data.Rows.Add(Guid.NewGuid(), "Company 3", "sample3@contoso.com", 1);
- var tableName = DataTestUtility.GetUniqueNameForSqlServer("DC");
+ var tableName = DataTestUtility.GetLongName("DC");
using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString))
{
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs
index e5ff3c7559..cd516df696 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs
@@ -29,7 +29,7 @@ public static class DataReaderStreamsTest
)]
public static async Task GetFieldValueAsync_OfStream(CommandBehavior behavior, bool isExecuteAsync)
{
- const int PacketSize = 512; // force minimum packet size so that the test data spans multiple packets to test sequential access spanning
+ const int PacketSize = 512; // force minimun packet size so that the test data spans multiple packets to test sequential access spanning
string connectionString = SetConnectionStringPacketSize(DataTestUtility.TCPConnectionString, PacketSize);
byte[] originalData = CreateBinaryData(PacketSize, forcedPacketCount: 4);
string query = CreateBinaryDataQuery(originalData);
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs
index 0d024170b6..532e0d06e5 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs
@@ -7,7 +7,6 @@
using System.Collections.Generic;
using System.Data;
using System.Data.SqlTypes;
-using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
@@ -131,7 +130,7 @@ public static void CheckSparseColumnBit()
[InlineData("Georgian_Modern_Sort_CI_AS")]
public static void CollatedDataReaderTest(string collation)
{
- string dbName = DataTestUtility.GetUniqueName("CollationTest", false);
+ string dbName = DataTestUtility.GetShortName("CollationTest", false);
SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString)
{
@@ -263,7 +262,7 @@ first_name varchar(100) null,
}
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
- public static void CheckNullRowVersionIsDBNull()
+ public static void CheckNullRowVersionIsBDNull()
{
lock (s_rowVersionLock)
{
@@ -887,5 +886,196 @@ public static void CheckLegacyNullRowVersionIsEmptyArray()
Assert.Equal(result, reader.GetFieldValue(0));
}
}
+
+
+ private class _Row
+ {
+ public int Id;
+ public Guid DocumentIdentificationId;
+ public string Name;
+ public string Value;
+
+ public _Row(int id, Guid documentIdentificationId, string name, string value)
+ {
+ Id = id;
+ DocumentIdentificationId = documentIdentificationId;
+ Name = name;
+ Value = value;
+ }
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static async Task CanReadAwkwardDataLengths()
+ {
+ // The test data is taken from a production sample which exhibited multiple problems around async string reads.
+ // The lengths of the field names and data values are important to reproducing the problems. The values have been
+ // changed to randomized values. Changes to the column names or value lengths can cause the problems to move or
+ // fail to reproduce entirely because of packet and data alignment. The data is not randomly generated to help with
+ // debugging failures in the test.
+ _Row[] rows = new _Row[]
+ {
+ new _Row(27967675, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "LfcK2K8CgjnXyed7Oi", "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"),
+ new _Row(27967690, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "Gx1zdwPUdvrgDaIHAZSVhe5P8", "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"),
+ new _Row(27967683, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "1O7MMFFVKh7OUY8Z17pNKIhNorz", "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"),
+ new _Row(27967670, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "2yF66o0lSHl", "5ig"),
+ new _Row(27967672, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "5tvcpUkTvlRdy", "bQl"),
+ new _Row(27967686, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "MigVFhYMw6jG", "2ZK883Zo1CpaZH"),
+ new _Row(27967687, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "BqqByWX5Wuc6DooWnEC", "PPKwf6FpnJfSTklfX"),
+ new _Row(27967684, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "Lwt77ImTu24Fv", "1Sb8VpRuiBoqcVXblALJW"),
+ new _Row(27967676, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "odZDwRGrx7hRmxR", "4SMlxm6uDqztEaWm5KgRstymBQh5yF8fLtXo"),
+ new _Row(27967677, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "Y2dvJ9z80n0ri9hR", "dhq5"),
+ new _Row(27967689, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "Dhhd7cliCo2E8Yv1FIK", "B1VC"),
+ new _Row(27967674, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "sy79bLYyrZ1", "t50JSsD"),
+ new _Row(27967673, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "37Wh8RDNCMh", "C4u4MFMySkFV"),
+ new _Row(27967671, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "iCQZR01", "GoODhGbRzTKk7qBWjCc2"),
+ new _Row(27967685, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "M8xQrKB8yEOLVGR", "LDJMXhpJvfGFKNjT3JDBatO"),
+ new _Row(27967666, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "FfrqRLP", null),
+ new _Row(27967669, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "QXSN3", "XCtCRP"),
+ new _Row(27967668, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "LAnC6", "Z4gmN"),
+ new _Row(27967667, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "fiE2vDYS7", "cB6hHKmoBiMa0dRG"),
+ new _Row(27967688, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "YJxqBBgPfdgqH26XCUWD0gHCsv", "ZyhKMi2fBWVrNdy44Z5JIZK"),
+ new _Row(27942037, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "LesQjDrs6rjgDj4MaD", "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"),
+ new _Row(27942047, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "ho3Pv3aLmCusWltGlgEAbwwpH", "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"),
+ new _Row(27942040, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "uARU2FodUEIb3ixMr401zs5gGq3", "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"),
+ new _Row(27942032, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "UTaQip0jFLV", "6MF"),
+ new _Row(27942034, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "5b8zlELFQw1rW", "7Y9"),
+ new _Row(27942043, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "IriW7xhF2aor", "lvxx7PedzFWUBi"),
+ new _Row(27942044, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "u8JMj7ChBMeJnN98eOt", "ITrPShAjSMoCTMfMj"),
+ new _Row(27942041, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "ugXGwPnQblwxd", "tiddVfujPuz0HKaTXQLMe"),
+ new _Row(27942038, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "Kxl8aC5uKR0zVJl", "QPZzAKNQjt2Dm6glZCdxvGBzpVPi9BMAVv4e"),
+ new _Row(27942039, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "3B5ikbV50KkZ5Ffs", "aRhr"),
+ new _Row(27942046, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "Nw6iJxi2wWKA8twN2wO", "7RY0"),
+ new _Row(27942036, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "Fi4DiLijhnY", "mNhLjRF"),
+ new _Row(27942035, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "uuzw6oLPddS", "0ADpjuhWDHJj"),
+ new _Row(27942033, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "rLxgGM6", "JHMtkwRTVoTebnaPLUUY"),
+ new _Row(27942042, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "U4crJOexXz6N0dx", "ZKnTZYlikloyaQiVWvYlbkp"),
+ new _Row(27942028, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "j4Ipsks", null),
+ new _Row(27942031, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "PU6Yf", "UxStUM"),
+ new _Row(27942030, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "v1wYY", "6HQA0"),
+ new _Row(27942029, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "t2xP2OnIo", "xl8Kc65mfbv4PX"),
+ new _Row(27942045, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "IhJwQDjAVByAKvnYVJd1fseUvD", "wZBP5xrHTmSd4zF8vfVXfw5"),
+ new _Row(27939369, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "5FEOfqCx1nSxMubDot", "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"),
+ new _Row(27939379, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "Eqc2KebZ3jFKtPXOCZtiNMJMq", "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"),
+ new _Row(27939372, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "h9zfijozkzKtEN0oIWWKtg1d37r", "75hbA9vNtGIewCjjm7Mp4LCS2vWbhJB6ubMLe19XcuKTEbp4KSnvjAuyXwojRHazK8xyQQ9cnJme3Jj0QDrm2taTztlpPQavIYcHGkKmn9108tCf7NaDbDY0txQ83AEq0hTF9QXdmRReaRFeadONPAbbczv3nCwwa7Dxs085XwaemWRdx8NWY7JhPJLV1JDcjsRjTF333xFQscQzcsoyjfaZd9ZW4PUDRrTWsfiksYaPfCYdCej6DIDuyAd1oF0jfnRjF3iTfHCTtwOvEbCNBYH2kPngfgRvsP2kLhMtifUa2s7OoiZkw67CzKZoj8SI22dYBgGsAluyTcBv7KGKtSyS1mRJnhUOBEiUFf38zMmZW5fUYTE3gXfuvFwWXXCxrbXNKjYnnplTrY58u0MSKipCFWeaC9imFTWNCAYHuoYqjXgrXGaEay7rIAfjTFpww5Da27Cfx1hB76sFl87jAgKoKBuQ7HmK5EMIXKUKKIhJqZQpgvwPnAXHQ0ZOY0i5x3HM1sChaHJjB0pzCS9b1DYPZwLvz9OTjshFzcLEjtNoC69IZ5qaj9wR6h1OruzPrDfAQZyqk2B4sX7lsvhxrb5PBRJtpKcJwAjhlNy27fSWl5J8EdqCcywOfdhXH1sAPgJvOzZVKVf216ci2XATelM5EWKbyegAy2hDWqNOp38ek4dLzQtUaLu992kJzHpQa4cyaDcpLoqYji4SsB0mpcVjDvm5qh30RbSyvYKwHkfzh7nIrcHpvoN1aOMoRQky9V7BNzqRaKffmSyDtsd5JufX1qMQseeHSiHaDUtN5hkhG1zjz8Dn6xOnSnMXetlJqSFktnJgrVClINGbRmIsGJoka3notXHxhxmH3CDjkNAgdYsl1VBhwAVOHRiCxknkqlfX34XrFLr3tlRJYHTK8UWPdDyWIvcd3Elc93tTru6rLRVTRcAHgckqZTQLmnQ9kS0trCZl8dryNBOOE27YWcxwUOeRBxBk5goxRYHKfAW9yd462yTpBaec5j4105PzsDRshvwGaFi1x0HIIp537Bczou3WQLnlAPNYoF86S66K0DcLApPQsJzHbsgXzhBJCHUMBV1PYKPDSlkUh54bTJb4bBVyUgr618SSH1EhzbXjpZGg2K5K62fJRaRmxxONA5Hn1NqJcA6Ge0SVbe04KGGB682dxhQFEManx4ayP7vkSvJZbFS5J1uciV9SwoRopvdg3HaKisviVajK5rhufb1NKDz9KdEQu0uEW7mzskR6FvHTiO4ksi2AB2nFxw1BnBdnQZoI0Mhe3PADFGQ0B9XTNQtQIJAD6kraJOOTqgs7BEYUuHdwS2a6CmD89eHxjz4JFPnf4q4wF4V8DORmxe9WMxDPiWbEt3Q1jahsbkBERAgew2Ji07m0fNfnXasaSjuoNxjtmBdlmXZ0AaJ1N8O4hNAPHKaH9O3qY4TfIFA8TLO9ViO9i9hoaEYqxXngk6ItqmCJk8jIgIxoT0w0j0p8YiRrqyqDU42eY7sSBLIIWZRgzehRMPblqd23U5UenbG5VVrq4l6OVFMB75Jh8jYYF1weRLGjurT84X9mmldnZhkWJERmmPF0BncJO6Xpmq3NFoLTB6XgWdpiOF7tqV13q1CV45wmU32e2j2pMDMfjxCMRLPvN7mbigiC7pMNeUQEJqWbYNAakO7vXPJ3IJtSZZZSVCxmq2SEquzmQh8EYNpFeYc7NWHyHiVsjgPfGScVRdBnI2uhLerV7NzlZa8aUyl3ceSC93aRHgywprrU97SH5XPogkQhgKwx0kYAySeERSkqw0tZTlQwuWjzEbDthINXD3ILThii9jOq55yeAfNb43VKk6YF8lDXev8EXLaTbptDEW3WQVuZ32KHHX3bP1SzMoJeTNeEgdz8wp9TCCLxOVN8t8LvziuFMA7qQWkBoxozh5rddNSdiCEOKNMTAQo9E2iioxYxSykayLOAtrLwuFd6caveHbzR7R29AqXYXDZOCNxMWrEzjTQwV9NqVz43FXhLReXrBJv4o8NCWPlplBtYbIG4eq0AMgwbIPAwyvQlHzQaMxJDufbxjaNMfZSj2TNpHC93ALvTrYKCWB7hJ1uxCs4r761Nyix74tt1oSQ3h5b7yDeZ3k2rUWojMSx8bjZk1qP4T3OB5Yh5T13E6P5Bge96JhJGRqNh5gmr4ok5AowgGPmuAgOnIRdBdb3NfMPtGs1J9Bm4BZV9HhERJ1hO5ZGWKfSio515EcfYXadsioFdI2jSgY8NHscWc2X5IGCbYemBKH9SI1nAUFj9nThJn1j7YWmCVihV3OVCRENbSZ7R4x6E2l6TdLpmoZJNi6E3jOXFBRBLuHlxTr2pxzspmPiOJ1yX3HExz5fRCVEKtxeJLu242Gm5hB2YXwHBj4C2jvYnJT6jgS9ahdaaBN2DGZimqIvo2qt43mKrIfb0fB0mAamgek667JvAXnV5qIAa0W7Q3HLd42qKXeAb6FNGXEB0KuGlcYbSFmAamt3v3f4mUluyWxs4rdMTtStbV85A6EkuJgCFaLa41y1HAZczfP1aeujVFlyxp83FQWf2yegwzetFEcQzSQVu49WDcYJ2a1iTB13Q9ZD6TPurfQQ5kC8DRcTl8hMzkFpxWFT1jPLMQWKDQUVDaTIFJLLG1mSekL6mo3HG9eiENP1N4YhKj9Ujf3FrdhK4ocNz9xL2l9RAsQdTlwtTcmZKziZ3hF3sy774vVvWm2bQvmPueSmnAtHJIOgVUGL0RMxDKcCYhcRczofpEGDF9omwJlqIaRnQlPOcHlWL7u5OunjNzNevfRltr1e5IM5Tdf50WRFMXyE3VkXGp66Xj7MIJYPJgg0qNDA0fTDtyzTheMwQLyC5rIEz37G2VtX8ik3HtuFQI3EwgoycmCwtjbkMf1QqqbDtVsX7LYReOoy7UznK2hK5YJgkHY4y9Ek9rkjA8T8YTlJuGqhvi0YQs5h6BLcRJusyS6CBxygYZPitIPSYUcckkq7esdWfnok6Lrkt4iiMsDi1dvlYchF0DdPtwMaDNTCFJAIAoaLwApR7hhHPRIdkVmX8BjHMLpncMYxYyj8sM3r821YaBSaHYriRqEbm7pyamzQfnvHNCzviTtgcHQLbIYcDgYUEBPA4th57Yyfc84SxcR2wuybLmdxeXpotxFR26LkrG97pGOzXQhyMugTH56OpJRd49A6qpXdo51IXTVluaUQoZxQGVCr4ERUuxvtf33kNqi0IdWUoTnR5z4BwXGicmTPPCwPzmBVZKdGdmdyIKFdaufHf0Qj334AdO32izUf0dGocMFYNvLXl4wzWClN2i96SG6I2ykt3GuK1RGZFILGOdV3OqNTAPG4b0TFHfdPhgxCHjjd2nUgnpJ6vLva0RAVrTxBY95dqs7DePKUsN7wLytxfCCVa7cSj7mje9UmGelS0c1KkqZZXD0KNiW0x8coC8GJNHRDnWItlXN24IVAXzO35yyp2oFiGhWyhNoTckeswcUOmrf6TlkCV8VjTWAZQpVkf1cxTfZLZmNQX89kP6tW1ohbALwsrRKzwon2gA573W1keGCxU2uzQWfyvkNDQ46tEBYUITifc6h2MmPKMXpZDS0V5JmJprmaKJSvUNtv5CuNnRVRl0V7sH20OYd5MAUws2dwaTyhRoESwNUkeWER0yUnjgDASk8f6V0y5WJJMCXhwEsgI38vTtxt0FBhVZucnDhMBZOtwHRgfcYG0ACFQbVSJnfpAEfdCpKID3WWBw0JFQdV4PynUoWofvwWZ5VDp2t7cLwdMuZYUNQiunBkgJSDwjUDUYeHlH4o4l7HRMVN8rmwdV9RAg6NpQak8ARWbwjUUBIIdLJQLRhCFNePwBadJRObtMvOXZwzaCpVyfwm19Pzm9GbkBlmKtZmvtCjkqmaRaatDkEurNgQHeBe2boyvSZC5bektmjLjCxgfwidJiOYvAxxgUhW2Qv2NztCgkLbxwdXoYKrKtrPCIn36ZYttSPC3fwEdbm2LmB8ZoyPiaSAxgyJIcY46Q69w6J8MOjOrFakQX0QrhU6zEJdSBPQgZe3APHnjcf5hgIqF9iW1VECXpk8ioYsxNimKuxhTZX4ZZvdjbjsZBtbyjKzICigYlrFp5oh4XxdjYr3f2Z5eKmKLazM9bWnreeR4jQGpmhpUcl8uSDrfNGoH3Ay2D0sQPJYs7TcYb7hib0xSvlDRfTZwLZsTPB37Udu73d7MGZtMo7vo6zwjjVMzyDI7zfq5VOViI8zA4AqsrZFYkO7iQvjt8NYKaLQMaATrrNmExSz3DkeIp9an3bJzOIIdnSbM0Wcp6kJgkABoY7BJUzqx3nPkqqnJ8OiftBBWJTsJLCQGe2dPxudI7CRT3rCCTD8HvSWJanNezOa6zkZMBkK7VkuUoWbVNB0YaVXF3aKPqUht0pAt1Sn1RbX0rHqXIPXsMQ7rzHaUGKbkcGaGL0SrAXRQE4hIczC1UyMM7Wsvedbx3KmsIERLbFPBlmvhlZjRRuNT3Q7gjjmCiBHHnHkZh4OR7iBT5WNIZb5XAB6B7OCkJe8mRIHJN267fo1M2FpfW1SnmAulzuDIq4yBAM0wgnCZCQXrJnalKdIILvPvuP7z6YJ2eWPWIaVPCPUaa5cLMkPNHso4SXwHnGo9bnNOfLUmEVW6dQat9dKX7DIvm75GmcePHQiUt6kW6Eqz5F0Xsv5tzZosubZpV6kS0zidhL8m1LJHDzockDHgfVP7vBMSFhJplugPktcMWkIOMLjsDI80oT1WTvszTH0l7Siib9DXfHuLCSQXAD1yXLQwoCyEuYDMGXzDnEv7DkJ1LIguyqiGc9YOlhfM6yFj28O3Ef36rIWKSUhRt2X5ZZfTlqQuyq3aTTJRHjfWAByys7Vc6C5oncEM6H8Gqi0SPREf0ulWngpK9SQs7d9Lx5FX3BGma6UgFQp748KJVlW9qZcfh4qol6FGnur3X3T4h01Tkg2qg5mk4JlwJzjaHnp0Ox1mx5ezHo3i4Nv8UDiDT6GBzmnW6CNuvQRjH2Z1yPrCu1iNhB4HYWj5rkzZOh7zQ3BogF4ZcQAhjYkJ6CebyFI1FnYgF9AY234H3WBlJXTLXqTHeylMQ3EKBBCwhhwzRsmqhZeUH6JiqnUCOSwvrrWejMCH8UZhwIkAZG1BegCLk7O2MkDWKIB8mu5yBG6YBmVx7rzB5rXsE6ohCNudrnLVwWtXHNR1AQ76qJW3z1XUVNtbGJzdW9VKPSoSBT6SF0SMYIfsfh1FiUqPdpvYGAdRuRfSzgePCnoRIoCUoquf4H35rwHmKUlMQ5i78RL5JuPmf2aFRQ1x8bZOgU7yPIwM6Xoj0RNmRdFe6sEsbOwMOdZwr8VBCwqQMMs38ySg1zbGZ6aUlKuQIYDdAr2VtClPBXOw2X3w1NEAkWDpeHBZFhfBDVuSe0cgr7BSss987kmWqBIw5dFZ2KTlcuFQOq1GF4D42Zgq0BKj9Tuu2YidIU6J4ZUvBVxxjtBRnGvfWX7xVLyPEjAAaFeHrb50vlDIaB4260c4wLutL3sRqN2gdzkbBzevmC8YKVxgPwsrU1yFuibuGA97Ma5LDAm6prCBM061QNGT6vSOsPcdoqiLcoGOinuHQ6TL6QE2CBTYbCRTfU9cUS9kM8kHkpuBnVlkgpFdO2y5fWKsadYoL6lUKcYhR7Ppp3rB6OKsUkK5xVC5XUa4fuY4rcMl1EuHPNYfQBgQssQA5e0ixaosDuaQ7E4W6x2TCexKZjZgiDPO8TLsokena5FJFUuEBt27X32xNmWMFheKu0plHoBKDnxh9NEdjb9V8aaCiwoQ4AiXEosJUMiYTFIsbDQpjhedZ1imOXiclXlci64Qi3PgFYEGuxbQSrC5V3WjnO7z2lJjYIgqYYSSujK1f30BgZFBlMSIKCA3R9mAQmpkzNWEcgEBs9HPRQcJxQ2Mz1dl65KTsG4S2KjdqvZ40kKZOWpoiDGidzqlWD1IoV3j24wGrZTxvz1PrMdsqE3OpDexfy8YLo3o4qKBFovXmUwdwwkAgim6fsKkabP57KukET3bfOlh5IO3635X2RtlFEBMfsqxWz023sL9PzIlNYkLD0LBQwf3SwipEiIsRqiBfsuQyX8T6GsuHQA986OrL7XE0YtlNlHCZTOgFxT1BVTtZIkJZMycwX6VjsAeCqHo7gxoInGBYguluf2Y6d9gih9GIGM6apy7QyODno8r87rAAtVteFP2XoE1nB434z2m92n4WVvvKxXQR1i2CgomzcCVB6uLmp0GpZrHYZisMYShOma2Ab8vLUXCG3rBdSkpakgagVeRkYFv2vusUx0GZcZvE7T2cZsOf9rCnfSddPBnwH1jwqq6FQbifZC3cR79WUOv5fJg7Xa5aQW8uN1U3JQcs8I2mLwZ2A3mQ8IcrOM9G01kAdix3xqimeSp0BiD5IanF1fFmyoqzvnARKChxuU4xavw5COKjA8NQOPkSDIu9KtYPI3INLmmtitA1hDqX9S3pnyPeXG6Qs8j6LoKxWGHVu2W4XbK9SmPVwATPcuJGduBYwyJRLdP3I1yRxZpkZJSQDU6On3R1DmyQhnL6zw2hGXDCuXaeRyBFmVTZgYI3tx81LAAlWQqt8WOLPFprmUdVbqpiu7YCBJ88Sfm2b2Ihny5WaRUsu7hs9yiVt3Gh9dQkRsYjENdBBvIVBPVbp9tguliw1UMQanfheVT1HXY1WVX10ObhvCxJWlBZyYDuSFXRVEC5S8lJ0Cf9PNq6C05cI23SKdYm1r17mML6B2sPs5Tn7oAyeJT0hjM5tMWy2YOYaBR8nq8a8EghDG8liZJkN2uMs4HxCoc45gtrtuBCfF3nlcr9py0K31HCXCvXiFWKmdWCvLniWJ4ApTjSQj4zFj8tOaijzwTvS6oNRtuAxAy6jVqan2LXSnxx2Qd8GucE4bBNc2Gkgq2ctxOtMQ1HPFILr0lOeKViRazSbb2XqHgd3wJVv6sbKctzcxdajORPFQZZBSa7z3xo4zRcgf3ikkx1SLZlASjBCg0omnHyY1vczSRs0Dlg4PZl2nQr6OGQi7tvy6qldqBbGwa7kchwLc04KlAD6dzXjaW5MJ10Is4ZmLFCBEkJgdJVZn39kCcQc9kK1PBB1lEu4IuR1KZOQLcDfdBQMYGnpw3xE8uphi5lenKnFIAqPyhBuJufa1pBNKSPYI64aT9mrioVAU2RlHRGf4iXk5YvPW1lptHwy83KibzYCkLMgJgJjGWQg5kPXKQy4OUd9niXQY3tYqIUNg0N6q93oPTT3kvWsgS34AgOf8kiNWfaAanCFgEWCcfCwWixjU5x4vUELSO8lAGQYFDUc3pVXkHhQMRBJhLIbpR9A9lKZuxtBkYp8YStlDQWyIwVkkxTZ4BVK7jbmIhwOwgdk6tQX57KQ6LtXXsczycZuqbzPhkhuyLhqgqDgi5zt7vbHTVY9VW7ZbgAHblaU7Zw691akc4jqIlv8E3wmHJgg2DpIKYHozg5kHEBsNbimesd6OaHWzrEafqsbokX4TBsFkpcPpPUaORt6N5kDqLApbJlFuKrrqeyX7eORfg1ImS0X6QwBPGR2bfWolX9xEMFhVY1MmyqA1qBcb8qn1U8nFjY8yWRcHMLYT8l0vL1ysjX7foDVFExufPS12khLARZgnH3niePtImHXhp0qOlXUio1aBGaDJVQ73yFtsBHXBvJ5CNMpRa9qtYTEZFNuppbmqiGGnfo7qpYzbhLmo7ipctnDQFdRLcWMY1J9yRjjYzAaZtVJXrZVfbzvfRfQXO4OUH3LTSIDWr4UTRvqm9ExPQkGA4MvrTVNp3roEMbTOBDvEOVfAYNH2yFmOz3hWaKUbnVQL8Wv8aYbwGTneMNU51Y39Mvx9A7KQLqDtzjxbPrD9JBTYW8PbymT8aMFPxBKhQWLSzm0cT44THIlpKDHzKS0Mw0AbyAQBslUR43n0fIgnEG9zWXZXGa5n4utppWVu90wnzlGowVOGvb73YRBxAPb3QmZ00zEusPsJBEesC1lcl8TSx8OedFVW9lrTK1kHsFEjrQodnstKDFg0Mmca8PvlygsJfDPOsT9m2WPWugRlmVoT1oTgOiWcZmcB20qmBwIpBq08bCVQAgdvmq7vWaiN00VWgiOkdRR8hKgyjNVxOu7VWRpB9VKJYzzNPB5KvxLSu2AvYkJ2Db0IguwwyHCNhOer22BEFJQmAZpKqe00MAn9NFGhJKkU493lkOkcgU1Yx646r96P99Vj8BVrk2ZoLJJoAGrF3jdKwCmVWqJfBoTKVCqCvzhzP8OjqSTUzvXcnv38iOpSDMe83B2xI2AMqGQJ3B98QrHkGCbjvmBoUstTls76UNU9niEocRegfAgqFGTOAwqz65WUE3CXS1NMUXoVIPpo0gJDu6zG7MXzn8n4xDrfe7RpY4rUvplf3LxdStmRipFzhn33IJlBnzFo4yGGKAVvOlR10TdKcq4wgy8TsLTSUKTUrXM0zDddkSzXfVHbQyj2uVA73qgvLfhazDhjscplzSvLLhPGL1WwokL8kL09H6XCr9hoONXayQu1Q7nNAyONa6WkAhsEDEpa3eh89DCD6odLZD7e31SclzJYIhwLmRFC8le7mo5EZiL1RuQKPKYmfwEngL9HIhqGYhyCgE7eAgT2L73PSwJD4qJzmXYrX4gqX6HYl1bXHewGku4wxgiRxrz98DfOGq4A6hO6M7rGnMxVx7zPMqND0PYc0VGEBEPnZ2BivUCyHlhcMhNye9X2XtOmtDDiJR7UUqYUDiRPRMMDgYfCjwnWcl5RnFpZq1a9SX0w6UOoSCTDDjX7a0OcBXv0ImkFj4g0uDRG7WbJpHmMzrE5Ddwuo58F1OBR20vLN1QqmcYGTMUYR3PTGujyJoyolqAIThr2DfcyAuzkorb7KbuoyJdIG3hLB4hyvNumNxxMxoL955tGvmB8cpnHnHgyViJXHUCQqEta69x2qITCK1s1XXNDCw9qiIdPC5oM7Pj4nOybF4eUDkdukr6lWb9delupIDNLb16uVPMzAXBg0bu5ATCi5povAxBzNlGh78rP265wJtHFYpShT9E5KEDYeOWbSndxm9nZLBmM7ySB5LNyuth9pl13YxYaxDViRJI5156gYPDSddKjYRoSGQtAF7pGdEdyid3YjAe8zAUiqzaofV8Jztc5XsgkaaSl07qXXljlpaYwIU46uS8q95Yl9gkEqY4KdBcSaVSpP831VjUTo71LDUgSdoC6KnLi307vONNY2pWYQvaw1SMfT2jHAga531lN2HLVJi1qAia6m9Qdc2DRqjUAAX0ZniWxH4KL30tM8lAwFb2IunpXh1rFw3y2OyGb7T1wHa83oXRSfbBqL94bxBSyFxDfh0d0JZ8H2xHMAPXut4ALm6OYR9fpoCvYHLpCeuCA4UXeJaWQI7RxvjA8OHKwIljuBjTS98N0IfAyNFWYfs5FGsFDDmXUA0qNPezyEMEB76dfT5CfYUiYUMEmETA9e0xmcVNFEexwoyXac3NQMbctvP3WNqgSWC9vsRWasLah0BcuoO27TYcBfhOUjlcKLSgYE3fnz05vN6zo3fcPv5SXlVKJ2fS4KINmq2yhjDYOOOMNbXGwcukB6T7Z27abdrjpgigkkrvw1xgouTJOIsLqiHeZKjc0TMBsXRE0r00IniKBq5xKHgkzhuTm92M9c06a7HFeyLZ3mXHGSUO2TLjL4Nkv5P18Ve0U5Aerxrr6lPFoamWa0H4XZTpXoeTsnwsIVMOeSLBhiXmYKZCp7oF58kPxG9yOsqQuUduYN1LtD8XWHR2NwGMke5PY4JxAX6e2EMgvh0940Q9642JcmFUxiKcMBruYnjJLPSvJIc7AVGTJi7Zio75XFUHDnSpu4hrwIrGxOWsPZFPNgQLmDygyrC2KboDIJKbpFHCuEvTf0ccXY9qYQezzFvsjgcUFWTgDh1zg5g0J1RfvN7AYo1ayw3pirQWAq6glLdNB1PngcXnzhvAE4o3hTfd6xIfzf6T3BfIKmcszqvKfUD9U1fhT93UwgkKar9IY6fta9DVYRY9Qlj5dXcFXkscoiiCVuV0zg0UxDAHiystwmokPV8gajt9jTe8Qbn43LAY2i5stUIBbhH0Oezqb9e9GXBSpidEsBxWl6YpFDOEK3gOyctDkjjFHbuO6CHXwMK3Y0wVguKBMAJQtjzxC0OOIPe0H3GBWYGlFua6pZhbrJwC8XoCMY14IYondP0zSYLcyHb7a4oPoKL8mrMLjZtJfMeb7eY2NArMGc2h0FOI8FyaVsCamEr3n4bdj4p2vEv7WfaRZ2NjiBfotAKpYEX7xSf6O6dt9jrMplkDkME7m8uoGmrbNzrgFrrYaF2GM33Ql6utG0KoEbj3Sm3qxLvV1JY89ljSBFMko5RUdTb0sNMaWq5u1wbAk6qjPWRr0F80fCccR4eq8pLdnk4bdY3H3z5ki4jdXxO7cZeCcFZG6cgOcya0jgs4pyHAK8AeiBkb5MCNkgxmX8zgI6y50yoyXfHQXtnF34av7oCYnrgvOct4Rh34nqflOx0ocD6wTR9tfpkwjOrnsPMOUtDUy8waOLm9vUAt9IFBWrL8egfQQl7Oql5tOrtTTsgZcZg4OnyCSz4e5XIFvp7vSpH7ZJHiQ5jiG80wHuXPGF0NAFXX4FWXAEWnzjhWswlllEbriTf7oWk9v1fAxjpaUyPhhBSsfqH8NQDwhrAwzxlEH4EP78kjIsUaDGIUTAoP6yXjRKh2Om9nZltGefumF4TfuyKDw9v3Fj2R5tK1z1CZtewd18O3ORCt3b0fSQngu0WcD7z2gAlr0sDabZVg2DABnapTk7CI201tbt9ZzBT3rT03yK8eI1rWxVQuB7s0ZbO2XOdfmffShZ1ddvHmnx0fA6VYM5gU5n816932ITscugImBq3bqGvy1yLYtoAwljj1K1qbbFKiC9lsf6VmUVwNUfPuN7wCS79B1FPN1G7XaVSCttlUMZxt8bLuE5hSiab5ZcQHftQJme5mTiq1FLDYN1QKSGcNGNlrrIzaLknmCH1C169Lpzqg2uiRndCjpEJlrKFdtjjW7itL66DvdQ0PYnP7L4lYyAanU9PgPUwmiriCKlj74oEWypZtSMxWIL2x2vGfBXC0GckQ7DjIfiNppL57vdhzwEbbKIeKvBcEWHd9lM8D6CmXkIvSzvrNBdfxD59Isq6D2KNPKpwX1j59t9fKcxR3ZdADV3j64YSgXMmXal11kn6Pw8EsXQYr5mw7zhDe9owHvMp4kr2sS7QfrOhTsvSKZTke0FQsI8buho2xvyETeonlV4Z7rhnJxPxstaatT6E7aj4GKJ6uK3mxzaVE56iPJO2XitETTun9LZ0qbG5QlIayG9io6LIsKKfGEGU2gXbLx4pf5NRCHDKLXajx7rpdpjz4gYqVQ5av9VN3ZqZH71RyznvHW52N2zYhXk9ihrtbbYRiPQuAEGGdUq0R4l7UctcCBPCHfddQNZEXa0EsvmAN7eHDMcqaODFUuZ6Av6a2VQrOo37h29y0hIGLMBWIxMoaXaXNdkQnJ8ppr5keajcXuq54j58y11Aeg3IHcttMzmmgsadbZy7n6UgqsE55hT0yIymOGbuau25YMFvHVs1tD6JBOYWC8kIzHYeO1MB2X7z85MFcswLvCVIvyH23a2ZaYN9eJpI4QGFG0M6kmkmHmNh0owwnbggJEXBUaHT0FdraRQSJyrAtapaVoqmiyZXhIlPFcYqkUT7IgnP7RoNpRmimIf1cogK4oSjKULB97uyQ0m5Rd4y8PttNoKqFSDqWugRTtLwRE83L4FvPHFbGgcQdKlaWf6b77wZLyGfudfxRIbqoDhEiflqTniFsVoMCIwIE4exkImCr34ejTRQb9H1kf7t5PI0gAXHOVISY4KYuL0nEk59VgHPTcWi4ZENu19xAnW0yduflkM7faAqHI5jS07ffiqq7FDOW7pFhTaHPUDtNMD8Ld6MrawIQjNpKkA2UFElSJH4UCTD744fij5H1QL1D1qAy5Ph8ptsZ12uRbTZGFaGZVgNOgPPIZTUtwD0PsZh9f5ZeuRp6RBFUenLQHs0mXImvk8nNIk1Yfx4EdmkoeEOjKvOpqcVyFKvbU94gWY7cJgxQ88vyVV7Vo7YVkdA93dJOgM50aF2cqXktMbfYpJ9iYNG8gDyqzyU1xOTgRNfXvyBoPRHaAqzJDsUdNWx704Bflat0UHqiDp6Xb6CdAhg0NCYP6Q6M77rkmLeCeJT09hUQD0nYYhLg9MjPJKq2Q7MpDeoCtBsfg75mjMxbq1tiLA6jSX9HMy58PM2co0wHkFtS5fy9wui5ssQzMRZJ66tM6lDU2fooxHgjLeJ55jm8T5AeCZSv6rq5ATaSAtu4GIIp8Nr50uWrmqalluZesABn3zNU1AoG4Vqrgo2Gd4cOYIiI7w7niS7uOi4rhsmnKC2PRGAcp0aWvB4ZWzSTIZRmnuQnNAOIrGhrQMSrgQxVd5z2EhMgMplM6MNJaRPLRSxm0WTrQWEZUO5mwcpz3jtjRrECsoGDr2V0Z5X9MZWkcbxIwTJwm33aqXbdGCfzG4AUr8lz5caNoUFnZtOb8f03tj9bGiccoteKNB1ytZACEpatOE1a4TMfGHZo8FfoJkT373aJ0lRoXqT4cFMjfGtZemVoIlZeQfBidQH52Hick7M64yHMfkHSyAVRFlFL1VNgCMeGkDjw81EcgEzdOpwp3fjC1uXcytd3VbKos8M7zFx1LxeVaJmyZkpH5hlFQRu8lPeMqUWqsWd9egzgHSj1dDSlJjPuacweXZkbW3dWdq5cDQMS9BSzC63aAaFv7I7HuNvcziDgL7LM59MYbNAU5iKF6i4IhZ6eWdOgxUmaRbFJE6GpxjngDv0RnFa2unXBpNha2fZUlRohttjfOXt22nDj2m2mWNoho5zhuru2RHL4JbYtWKbAo1NaYI2E1qpxxKjJiA103qpkBQ8YTAcdy1tyrXB1alNHKdhVrTxnJ1AfNLwWbSaSxf2ubRe4r7t6kHvzSXI40yc5aMXWdvst3rzpJwJMdvVxTicwmD7IY7JRS4s2ZzjSYRlWSDDSaKrZZTBBacgD3uGUfy7sKyMcUQA6GhJbI6p7VNagdSCZflq2QEOmnVXyFe2Sw0wY0yuqS97rDjruTTWpXT8A7Yn6NL2j2xah4uZU5B6UZK55cvTyYzNLlBU3hl1Bv6oEi4P89gE2dAyLY4FgSejt35X7Ai7CbY0DiUrLdbCclJMv7jYJVp6LHPvY56J84UeoXQjq9E5sJ2z4twBLiMGcAUqY5BS1ouNmazlYFQkMKtWdkLBhDRpvpXuHdEjQ0QPrvgm9soFHA4hASCoh3VT8caRMZzOtZS5HmzKHbWNRLsnzyNMfBIqPeoCnrcRQAsGUd2Sf5ZN5XvdxqtvBhp74KOv6lCHsqR3eyqRcGvHumWaImo4b24FvOOLdSUOG7HMx5vjMz8fLfoEpr7Uf87xiNpy8T7hAuL7G60LQnHhNkUQyByZ3bPcFh5eazrRpkAk64i5Uz11PomCBe3WdZKEJMmmJKMPVijN24SNs4eZlf62AbReG0WOUYdzeATVaiKgVCjS71R1l2vCSywslQzxXZ4kiOuRxJo0y95RozaJsEAtuG1LLmCIR6C0QDDxx7vzUdrF4mP6aubwzLVCCB014jYmwBNMjJ4b4bSwDVswNMOP6zF5XbJyRjqFrAoGjsd6l2yM5t9kjWmPc9taL651Mq67NVG45JQp7OPi8ebi2yuhdStoYa0xt9Cm4j6krsDD2Sq6OXjHsNmK9BZibfd62kdBro71QSdoAAKr3uRgKYYPYVIiPfshiAwor40DdulZ7IsshBmARlA2McS1rfXqhhL51YA0OXVW4V1AsWW0ikZPzWrPJ9a2FKgKZsXguhQ3k1i4dZuwd7q4cf0mHKA2MReD9BgoP3n5BqbkNWmxyldmeqDsTsgo7CpehiGZ81qBpFJd20Muk7N8if8aKLQMRN2IjTGxEIBWYBPbGcMlMdVC10uHiOShnM40UrMmRnorGBeiU8OYL3ghheDSfu8Lweur9JkzOttQvah52kqfCrmEtDNATVCJ1sPnlYQzCXxLs1gqIpKniVvK7kCIAA06xbSOEST88T0s8cUpSlL8feuFJCNEUX78ta1UiXE2YCfxBO4JoyPhDUVKY2ti1pzZe100RpUVxHL59PQWeXdmyUUaZwLNTSZvN4bwzbmMPU2K13L3i4IwS9cux2xiDiwrxwMyHNKUtiKwQlBqotmh37dUsdOs8LO7EoSReHQN2K3dz0e1lIeeCyakki9MRyAXQuErDRXQXFwXYihmmBJqteRMf7zTJpSqoV1q8KhevKF2WCVncVokxOZmmgVGUDUzA0UJNO3dXKsDeGU5t0aSfIlEeGLzw8EK5nzn9f1jCkW9bULIWAPR1EurVRqB5WEaO5x8g1W1O71SzVG2wzLtOEJ43DHlHErRAQpz1pCw9UswrfGaHuGpRYwpCWIcOOb8d27TO5FOMs4MuHEN6wDOYQL94yJRBwB79ItahotRh0qsRIA6nWWA4aQg7nbosTBNl64q7iRe9zBGtet9MquwtLvEwbfG1jTTSVrdeistlmmJGujEmu7CtwpDHAhQqsfIugbJSAHzVGUGtployeYJP4wcW9rnZfwzA9mV1sl1HhTVKY987uh1dqMwbunjQ3qxt0zzQPix9yNnjKZq0mqctCqoAbKpDCmmxWY8zDumAkkVeWCxCKWCsBb8BRPHJsKqFpwRSu1YUaObluE2q3kwxIwOjA3eNbnvL0pEl3rKhdTSkSkuLN1ec40cdzHEEnNGiLC4IErd2lu6hU1qNzjjOBhjlaIaiKHktFRZzGzFfG8uE1nG78AI5Di38ivZ2FDtbLxVmwcMBRrjZ0LyvvCTxdfznknJnEudxPQfRHrvopucbRpiM4eRE1Ocj985vQhhVTpDYxUqEErDyiTsChQBD0hxxHQPDdDNSa1FPzr0TUlmuKf9qS5jktMiy9TG9XMVAtLCmEc5H44m9tMzjtdyXt5DlvhzEjpH7nm6F5SImMItPWc8PiZKsgv2IZifk46tTFZp2YoZ7ardnQiHMnkjHIw63PbD3b1SZyyVUlCGZlmli52crXyMFxLZcwWxd0u0eBj2yYw4cuvIHiHYG2DirA8LG4ARR5QVyXRNOQmYGgs34JHUjYSwT5544J4iJsGVNoKqTx6nYHCx2khhW3jt7QM5BqnKJGS8d9DWk3AnejioCVNQWsphg5xK5CXw3DTxfeUjMNZ9Ot67YB7d4rgp0HoS5HR4bwcmGW1KWb4dpe6zVn6dHljPxfaPbFEm3O8gcFnBlaVm93wnkmQdN6trUV7kZtpynG3lEt5T7YduxuVjSYVvXlWWiZn2pRa92yAeeHWzHg4OTqsGd7gMTPxgoUpL7vIyuHquyjxPDLpYAgJsTNZfmG2L7KZXSf6bK3tpMlpfgg29ySJjW5EjJPUyygqyeWSrl64gwVq24E3iqyuKDmJROlUFhZxtSQSQYVteEc8F70tkNz4cIU8fBm9HH3t27sHPH3z4Vpu3jwIridsHux3jJmmFkWRfb8OYTkyej6o5hafiWB0pa7pxeoYBXpO3PpBOU69i13urYgTrUJInQilhQmdoEUiJanl6mx4cfFZk3mMLWpMPHK36cJwJbXYFpUI8fo2Txc5peN2jDRpBhUm1ShBMH77GGls2UIZ8JJ3m4hrBQhxRDUcmnM63Way9AiXtaVqaasnKc7KdrfVwrZP75qLBj795jH663YmprE3wQHUNUIwxn8iDk5GEwxyI43HQ8kxrheLTwrhqkwt4cdQ8lc2sNXgKD8fs3ilV8oXrCfLjkdhl9CIBjADN2wl4D6Fl8gM9gqaSLigW1O335epNyGjW8t4blja1MA8mibYHIWAE68LUQyLG91EM7kUmYMJ2Suxn4ZXu5WVkhNz5jgukd99qL8LAGz0lGuqvOZ0uQEPLEPz9VMWb3tFzjV4FxQLyDLnawOUvHOIOpdoAkQnXYVYeICe7JL7LeshD9TiK5O6YeJYPjY0uVjQpXH8KYBSJIiR9u3Nzvvvk6lPbe9kFOlYsx7F9f9rF4qb7WcdAMi0g0l5FOoX8GdrVZ27N4RMGHVecAB9PVXf7BJxeo56c6XGwrp9K1MmyG2mcGGMe12etb6fHZqZ2Jo8GlPU3xnbMewU1Z8f3Kn3kzKXJTsVu2EebRqrlqnu6BJ4di2Cjg1WCbZl8P57fVwWG7WLtDtInaRyrtfBRXZB40SxuR3Kum6uvljYIY90Zm8IMR2bCWNy1dCV9eMiBmTehvXlPjddrmm5w3y3ThGCUHN4XH0llqiPVjPT9EEiAme6efS6j616QbXYkLNdS2n9pI0Hn6rvEt6HLr8bPMA10lbj4kNYIngbqD2ehco4eauZY1ukH7GhDPBoevNYN7jAWxh8DxYHaZS5rBqVunArGkENm2H5Np6OJAN0yH7tvg3Xm4b003T6o1antWAJI8mK1aKlfSAukX9xkatJ01ik8T1PB0JKEoe35glJg1b11jAeF2pmCVQIpzxXkmTDDdMN5NFTqQ9xrCsd72MFNN3DdvxsEYCqJ91hhLGp34xRg3ag8n7f0kG880RR5nNAt0HLSDivGmJCz9H5lpGDxFI43Egs0MuBTj6L5uygNLrJm1vRC4zBeForebtnXUnHr3hsVavR34GDGBiGEfsAHY8YATPxhpyHgt4HIqd1pD5R6XOUXclPGo5n7CFFGBDjaEWe9xqI67mRFtR025t4z9kqN0a3RUYaGd7Z0d0Uqxl1yzPqkThLHqiGxG1ScaNn4Fdy9gpXa1mdPPYHIHh4DMIhS6YHAqvMQcIua2o1eZJtDBaUZmXFysQo6As54xrRKg93irJtJV1NuAlRwfhUqWhmLnYBZksggzP68VjhLUo3KhkieTMb4mmBt9H9PuQtjiVQqGaI3UCh18arIlihrpWqo7QermrZkAmfSx7rkPn6DxAGGVZxL9UWxY4ub6ToyZgesxbJZGncl9MsHnYUciQW8C5Mz0n8zEtzXvgRhXKiDYwUkVAj816hNFgpWZSZsYwwkiwewFIFly0diW7olYg45f57BHL0JozHOBEvgwS2tUD0B0xZdhWN05eKybBwIqDSxpdVttLqOo1EoywkEP8uMTAyyYBsHQecNwjHdbiZHe9fCnFvQstlUxObsOjkdiOy35lNEfZQckaNRoPcY0wZ321UEwEE1FpJHPQpZ0QNW74wFvJdMjGrCdepLvcrS83Ae1HFmYHxNUjw5l3UBer8AQucPEJEyXLg7TN6mDaM7y0nbDZ3NeCF7OPvODzWOSQzbyPHYusngH19QMLaHAW0yhMOSPRMnP0XL97UqBOsUga4ngwhcXiqtf1VsxXeVHFZVM7rbXzTRLGQUztNpqUghJVpr24myVzvcQQMLiSIPZBwkP5KGioG5eQEz2bNYUbWb87bpUtv22VwtQUZhK54zvkeeNtyH9096uV9GZaSkVekPOx6yX2Y2vzHF0ozXYkeg0KqjYg1GkNaCpOjzX0lVxttLUe5b5TNupcKvti90v7uDDBisAdm2INJC6SFsC0KzRyIpzHIkDUnBRL9xypWWefudApad5lFFw2xeWlEXsAnWTLjFsZjy81dckvu6sp7pmYrjuXPNucSHfLDAseUwmmf8Gk8sAayCUfwLHEM8HlXVvh4TVYLEm08pUrjoVpR6b1HnIJKe6TvJEDFWx5WsvbmfmBQtHMWW2vjHptHw5JFHzdCrQJffIO0JSpmSzd29dDE55eNMRDr8ae1Pd3u802PRlHTMEDIyJ6AKv09RkhESWLTFfRnx2pbQ7GximQMEt3yBOVzT9IZpu0bMGPl2C7Cej5cLNYLSnSSzrlR9FG3CBAl0UfLa3jtldPy6UhlY9TLNlb7A98WTTKf6ny3stPaRr3jgZoEKNFLNR0zGG5jcCJpvmX2xpnKv6Cbj1ZUzxPTbq4xqfD3woYYO5xQrSGlIt2qdSQfJndqkXzleCyUqU6GjzkLjHPcdbcNsFSExTjPFuN8DmRjmWXNMcfdlixJLHqhTb23o7KFpU3eXxILqAK48wlVpRyeVFsZe33UTYWQUC4vw4S03FlIlGHlSfovqwG0Akbbt5woltgxwzW8zWhqbi12xYNGo0AKDPjoFRHqmVYsZdwxTjzSb2YTkmWvErqDpTJxjfnFVEcaVNv80XzUF8lcbLIyp9snlZQch4glJ1TIa68Rp5tuD4MwngqeRWzAHEV1fH5vxtK4cbSv6YXuygmT8u44xs7dzaSkRyBnScPBRMyLCPAJ2KJTu6H3GQL9vedqkSjKPJ4vSwu6hmvYJ8qXA4YAOngwefraROe2oP4Dfk2PbC1G9jXI8i6vwh7nnfC0lPQhqqKgl4HHREni8lRJWvACUbwUQBbAI8TNAFL9cPnMIwHCJsdoAFqMcmctLINBE3OMXMyIyuaIcW2Zcdkdvan5vDsculmXicj8QBsgifHS23yLqd6DZL4JU14tdPNfJdPg3eKAhRp0EzVMaTzH2RKknXEkx7TrbVJD560F6JQa611noCAWOQI8H9YOgYUMu124Ay8nYqSRSA7phWlbYhjw2IETxLcsbf55OhjosFUSNTMgqMYZfQD8jPz77a1zR4LQz0wKER3OKYCV7bwvcEK5yGSfhnzPSF6RxUmfWkCPFfM2vrtD6jM8F8iDGSWSMGSwKUBcg1Hp3dnMHyW33kJOAKyxfvo4BPm84NWagmeOp5gmi0oeybWMYThdXXiHMH6Y2YYTYhvn36ZCEf99TOdkrJ1tWevACcpP52A1TBT7mRQuNF9uF6grNlNy245aw1eO9ifXaC36gCdRO2meHJtKZUUg4hYJ93iYPblApSsAeXYQi8z7HaoTK6374Nzp5vApBHfnQXk69HOlRrr4rPXBSOSDnZShyww3EaGzCIsBgdeTE1fgiMI06Qkk8KHATLmkmSd0YPjKCPfyVsI8b7ze78eWPo6KuvgsXZy6zeNqnPuot71YQoJk1v648q6s2kBBZFbgFcQ8mxPQSkWon7UCBB6I9KNKEY2BruSa1MMesusnNnlVkUKwXrZX5FjB23zA1TUtFOv7DRq8GQEU4UQyiNgWAJKyeR1MFaogIzH1kkfRcISle8CjOtnUWfQZ7RfwCy1Mql5EmMLMuZpjPvoA0uGZ3OwSa0DxWx6K6CdNfyKOsNukfNqxA96qPHtXAYF3IRNxnNZYwQVn5XVRV5DmjiCBd5wHOw0Khbi36nJxKEDQE3EA7uOuTpyw6gYHMO64KOptnsD5DHWuGgJKCG3MQTc5e51ybwwX1c7PrmanESGtekQdpG00Bc0l68fT4T5MDHJrmW7zzMNIabJVSe9FjfOkxJ1vQPTUQNP29VfmJLDCGuo58pZIoB4J2lt8yWzpryyk2XN4nKYK67CTkTBAnjSXXhmzgnfJMvy5ei7czEz6eKFDeaBr2hCAQiN9R4kCKlKxUVllrjG6SLL2QM5Qnx2C3wOKDfjfy7Va3w5nnO4Ua7Ep0v2tNZcxUHHTu9Q3xjoJvEAcuEebKDW271D7Zev114sfRSBvfQoVNb1A3NxkGWYYvYdLhqu9hVW6P8vw1BjiHp4cLnmDbITsQTTC39yKhyzfpk0FzDWHgKryNuF8EfkZQrT5vcnyY5mNw5wWU62GEMXAadDA8Hk4li4DOQRHdBZJhDk2dzmKZNyhn714YDvPVvFlQsRqIi9T1rZsQwW5GckI5L54KSQ8PntWGrwyAS7C2qV3MvrxvuaxhN5QdNDokqEkNElOlkAK7IY65xcMItGfg1xy3W7YznFsgDVEZArwoYbp0a47hhvqZqki3ucLeY3MaofEtD8RzamTQIvfu4ongEbIR6O8v8QVOgtbcwD7bZjWYoGf5fVMaWXLBwteA1ZomSIoQxQWDkt6PQMki4A3KPBe8tCVNbTNsPxE15QsPKCzU8CnMs1nM5SM26Mp63AX9O7a6ukujHSvrVcqUtuJk7paTLVHESMc6BvC2uAaeInX3sS1msfuCWZAf85Ffw0HsZWnnVINYFnyxAnmyVZFSLRbrgMzLWYPuaEqti2PBXmDqh0rorrWrqbeF9w1gZKX2TJjXoDhnejI9byQ3JyjYvwytWE35CEyV3ZMkl4unlloVjRNmj0C0HGc1gRAMb8Z7XscWYlW3vhTDErAG5it7E0loVNT6NxRTrfHQ9bQuyJt6Y6jdw45wiMmEPZEPApJDw3VxW33vuEBuz1e4f6PfXZxtptn9nwpBcLuv7FJNabQlX4iZ4ldRYNCcUqMvvDrr9IWiVoWn6ztpcDIm8plVBBAIBCxmetqEEMVD7vixgsrYEQl8EUf8bK7zpFKaFd6AxS2RhMnsQzt4kmTlkJZZhgmoTSAw93EamOmDLXv98n8Bp45ztRRzNoOwyOJXzmvBAzPXABtDEQIKR8N7uE1RgD939vUkyYwipL1NYrldZJM0CdJ0Wa8pygCX89rhVHdT5ERFsERL6r3s5vAA8FtDkhd6EczKooeaLtT0zI6KWZ63gboZYRU0gx9mSfaidRnQwoa8q6hC87CSUSYItYgU9YOPn1aiq38dwenVpsBMOru79ekeXMObSW5smaoM1il6PhBLpCsxYscton3Oqf5sjxqKG7I2ih09RbTKVIRaMs08dyzpWnaY1yT7cxHy0UutdFefGvZb5COE913hDkk14kFd6RQF3LNEahEvy3upcZ4JCundVG8cqKHMyLDvEPt3kyasPKZuHV2kJNUskuDonlDeg5Mx8x1mtanKBSq03VeQgmLXwohFHHroknqLBJi3luvh9Q1vXqTjp5mj9s2DdoHKB5rrPwgPIeumGDoyikbe1Wl1sFL5wjkQ06HR8VVEjehJdrXu3cc9lSST7MRN8kI4pcYPyX0ZXYOqvGzKRAlCGOBr99BI54cUg7Jfrz1QOIEgmk3cY6qTyw28TuL44l8urusPgVFZLUb9gW4jGEC6uhgLIMOsJ7SSu6o0V8AGn4tcZdQOqpE5u83zkeQiG9n2fEHGnrUErdeNsVwqq5GWQ3HDrYLdVEpBXb0sCIjMiNJNgvRn0rSWljnKO2ta04p72NykTLzM8FG1iSTxOhGsEOIUyaNEE5OC4bZ1StVZfocjkhVK1WrhHEIBYYuZSgmy0ZCHZGrUaeCXBqJQBf3K1NYAUq8H399McBqvBw0I0UasrSP7K2FfH8NKjv906oSdRVEfEga9xbAqf8q8HvVvnzHcqt1yYU1gfALSzf3SWaCyKe2KE3p59g3eHrhOuFLvUUMOIOYnUnbsQfYCHjqcOyh9c6BydWRzhCcW03M7Gh7c4WMT6qWwHWfyKqyVhCSMsgzUak5k1WNQWfvm7vqtNgCWS2YKEwNT8Xx1MQi0XHrLPXhHLWIPA9BVx65QzLfoTDakudumWzZZ3N3X6s15I3vafYXsYlOzDQYe3J2nbNtHnbULIGy1yBlKXOmf3KCLUJ4KlyZgYJBKGTA9fOjXgLFIvYNEhSRpsVgeeT6i1fE7hTiDI4McIs2szzHgms9Yi6y3DIYIDcputfqiouDwVYnVbg3WKKj4lszwwA3E9xpyBUFvfgsQXonINu6liwFiHXQHR5tRnc0kq378rIex7lIzT7e3os5wr9yWziYKiMrpwzuDtBdYWPNpn2NmgsDqWbFOcckUUACF5qpPVoQJlderEm4djUWnANPxoTk7dT03nLMXNddYuWlvrFWS3k6AqoYk3jNs6wd7nZmqdVYzgNlumTfCcSEauiEz2xueHoV9BMW4suNIUa8NOvwJz5DWOBHAsg75IQXxWb990GEDwkQ5j54MIz803TsNrI4NLRlnnPxI4vmD0WGLuIikl9mDRvs24BGMBZb8114SLJvR42XeGUCLr585Qvt7lNpOZESM4tVRXgtFRtlQa1bONbagWKDvUtwz14BX24utN6cjr1Ls7gcuv8S7N2myK7BjUkNKDK8Zyr0RpmQuUllQE63WKc2bbQdmyGtwlgiueVjncDICprVrsku4x0uJovmRHlsPFEmh0EU72IMA2Mpwl1EZaXS4I9Smo83iNd8YcCgene8M2Z2OBvJa4CqDKlrHbl4xakjNvhtueMctaZaoRxo5vLwHOEaJcvN2jTvkf1ijn9Tl83f4StmmF7r7wrLYU5BbHpbVBG0cuqysMixMHgN77uyRsL1yb6fmVPqBWInlY45xyAuph98U5W3vb6vUa4Ed8HeNPdHYFjIvFcgslwNC2m5CvLL8fyIhT1ktwM0lB48LflO3J9MzwKbxVZXRwEFK4wXYzkLPL7K3CFGbRoXytarT2J3cHtQMAwCGSfFM9EfDOFKfXqmDvPJZbYl3nIvziwQOaTTR7GhN0QMfpT8BmQEEuIluwGoP5tBxGyRSv2MiuxSzLVtG0VSeryiqllWvfxJ5knUN76ePeNXLGkdAcPqaxLyNjXNoAfh7Lmmd0tvF4sc1rE44Udt4X2FVHODoJkYMyaDEL5oJEddywXY0svFealS9M3YTVfmHDH8mHKj5kKEVpmOIYglYpmg9yCRVPEJCI1EHs969NV9iwUsabH8KJNnJfKNcTc1ODTEPMjjIoueZ6oXhENQ7W28GxC4hBw7v9HcD7GuoUWtCgkWxrKF8vXLp4cye0aVLRyBxzaOhdaaSZOnUTC5N2iOQrgjJiopFFmi3TM3cLgX7pJpyjekR3IYe2S8frTQMPRRyaXdj4yexXxZfL2RpWjbb7xMcUaTb9SznKGH4mjTxiK8yKek3VpPmbNnjluCffnHolzpcRZOZzTgtuNt4jKMkPIx71iAgj8uI3DbSbWGWzvthcSoHb4lX7i4RXGMAU9OefYfmEeUPuVohehanZv0rAGxkIXpp4Wz9SFYH2GVRFhhRc0t4IWCLKGmlevDIJVjRqfK8XTfgHAqDVetb2wVEuGF8KTqowd1Qyw9xXN5Vpgtn1CoSNvyqlgqUBHBE2kkVQwSSas53VDSo7AlJzT0RUesj9P8KO7orYwAuRv48o5JwfSXcIh7PH5t3PWimMH6MY2kbvLd7tRSFXAT5pCKAJaAtThnaynLfd4oBgUaIUwRicCWJP3bfsUWROgbfaDFLDjajDgyIxddwpygV3zJ0mpByMaqGgV3U4g3VhvcBrJBzuHTTZf3dnFCYvBsvW7lETXqw7FqpBObfbOnlYIXL4MIn2nk4YjbHwbHXX0P8mY6obb0n81Y8h3yDOvkFRbH3KYfrCNN7yKXD5IhYpsQEaVgFhahMYOn83dbpbouDrHJYDrigUE4ZEOA1ZUcK6TVfK52i6ydM5lrSvZQARZiAdo838fNL4J7LsAePuaeKVWyaPAQKkIsUd2uBJTuHBv5fhr4viqVRMeZfYf8HXSEnTrQuV2wMCAymYGqLeP6Vwfn3xp6NXHQWVpBHS5ShlTdn6mwftpMGstaJXki8OlSjJ0lAAENbO3EBVW9UevQ35Y3jNz1RY770dXUwejTAg2oxuBpZU8BMcyc3GAOlUe1f56ZUMHjPfeD9Ilsf3LBZthgzgISJLU31lGYClD6CgJrWx9gINspx915KeaMDdG0Tif6zc1mL8B4dMYS0baKgzLucjVjdpPBqvOK4FGg06WnFqYUfKmYGot3dGlkwyiTXr7WoM885KrIl88WDJhN1WL30Vi9Q78zaTnJg9XCDxIanBKd99ukN7ydxBeOLwjaUtbESMy37pXUqo02ilEyXPkxwlheBbOPyitWlM5P9k3JukEk0bl5xzrA7IYM6XRqGEmswweuYPKVPCnA6DT2FJMSXaHc4P3afv6Os09CticXUxj9wwYHs6YpkYkwDAhpbWWEKZllNKt59iSKOnschMx5hxNqIVjeyVWDISMKjGYNRY0JywXKTTBd6LCmDHGCz2UVmo05bTpfAx18P7q6XhEUxDB9ZrVhDw8h314P45aYYNaKQeLIEnf8JeSUZf9wvRO4bDCr3zXFG6yIABiozNmRpnDwPs7cIpfNT2W4Fv9CQOg721wGKEdwUKgoUmbIgUolJTmSv1Ul8WTM0WdA8xemxTxn3ZHVxRru8EH2FgwwPw73zj7aMMbfQ63ypqEkbTRWpnX8J5hzasncsBjAipeKykLOaCoQ1UDmwDQDl2PtE2EBoXjYDcPD73a64Q1THlSTiWTrkEkruG2Nd2nGLuI3jINI6jcagKRisHjxC5J8GptHWjz8SixjeQFt9XwUjzCWE4zYGgSs6XIcMhzQuOW6xghGcy4tHjZH5cYkR29l5E7wssLNVnTkfdvE8Tozq4mFsLDlpi30Z8nLXOtBOlWn91WVkkUxkm23wcQmn4gK8iDGLMNw1u0LSGNDeswcDwld0qgbOoTRzuWA2AyH8ABKW4Er0RY2Gy1x0HwyKOHRgLpJR7PBx7QZASQU8JCBrhdaIgRU7C5tOSqPFH671hLp9dgugda5HichqwlziGySzPB1fSlIogY9NEoo9j38QPYtXJHPlcjaqOoWuFlcJI2PHTjrtjEptW0smnyenvLt2Hn1EE2tj1NOwQFOZpxdOzPcEUmWqW1Omyr2NCWSBo8CH66HQCwkWfiScs46UivFLIIBgz4JwJ1taBPT3zwQmpvqYGX5nRBH0NWG2H2kEKyYLUN8G0BJhsSy0Q4EAPh6hkakORZUtBkX2nEvW60PIPVnANLZ16I2SbllNYpZxFAAYzsALpZLYmrcqRKGHYsZNn5Zcefr94bgEwpDCUd4eIsLKAZqImAMxEHvB0Q8YctYPTtKiFoG566KjrWXiwtHK4VJKA2qnXAKULFm9JqzudHRH9Hkg278IwvxLEeZEThJq2IIaic7FMchB1T6b4IeGgVzS31BgjnM"),
+ new _Row(27939364, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "pl4SEzxt5aj", "y2i"),
+ new _Row(27939366, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "pHu8rACwoAeAR", "6P5"),
+ new _Row(27939375, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "I5QPIDh0pOQq", "TyN7SRqp1e9xhN"),
+ new _Row(27939376, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "tIs1cRUF6EQXIZLH5qL", "WdgCal1kzJsBfiLYg"),
+ new _Row(27939373, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "7AAFQU8b5xQjz", "48YSumTpuwo90lHwHmcuW"),
+ new _Row(27939370, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "bUkjxvr2Ij6Cb7I", "bQ4bNrK4yQHdJ9WTB5O8JxitdW24vEoHFhii"),
+ new _Row(27939371, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "1aUWwkCStvQ0aa9H", "PKjk"),
+ new _Row(27939378, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "JHNBoMSpcgbYBwqCTja", "0pUE"),
+ new _Row(27939368, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "VETrliW5qrS", "W7VBDVZ"),
+ new _Row(27939367, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "DZpLEj7a7x8", "fske7dTzHB0V"),
+ new _Row(27939365, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "BFiGUeE", "6FfoJN00SRpBwaeJMzka"),
+ new _Row(27939374, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "0VOL3LAUw09go4n", "joEoLSBPGNTxv4JSAV7YqRo"),
+ new _Row(27939360, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "YjKaV7r", null),
+ new _Row(27939363, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "fujpL", "5d3m8k"),
+ new _Row(27939362, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "0nZnP", "zCOIN"),
+ new _Row(27939361, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "IImbQh9yt", "8fh5FpTwXX0CT"),
+ new _Row(27939377, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "nte3hSSnA5qUDrTeBps0GajHFV", "b7yn1aDZ0PopOOwVSSFwEZQ"),
+ new _Row(27967700, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "6PsGeEZLzUcnKGKL0O", "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"),
+ new _Row(27967710, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "4UemQLTq1dAnEVC19A6BwVHaG", "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"),
+ new _Row(27967703, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "PztxRBuJCcvIULKp6mUwHuyHQHH", "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"),
+ new _Row(27967695, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "ZOySIGfxJTT", "VRY"),
+ new _Row(27967697, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "Qx3mI5MQCPF9k", "xVy"),
+ new _Row(27967706, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "01TPl2U9dzNa", "bwDlLckZEHqokP"),
+ new _Row(27967707, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "jgp5sCGrgL08A5QGfM5", "OM1DT9iSMoVZRMIGu"),
+ new _Row(27967704, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "ffXeEmkW5ZiRP", "Hr9e5U4qbWHpPxNljTYUz"),
+ new _Row(27967701, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "D83pFinFz60LiQd", "z99W2HKNyBShCLiqWrl3oKiOYmhK6TlvlxSC"),
+ new _Row(27967702, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "h8kGa6byq7u6xwwH", "dFXM"),
+ new _Row(27967709, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "dQTHfJT3W0ZoVvphWcL", "i3sH"),
+ new _Row(27967699, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "iNlhTgyYc4f", "BJkaAdh"),
+ new _Row(27967698, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "jst3eaOfwwy", "z6wGAgKIcwOp"),
+ new _Row(27967696, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "GE5DZYd", "NyFnZKT73M4QV5Ih3puI"),
+ new _Row(27967705, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "jf2cClMdD5wiEli", "M9ovCXpQGbLw52b4jlVS9eK"),
+ new _Row(27967691, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "tJXdP5Q", "boSRIr1E8Eqe8BCPCdYDi"),
+ new _Row(27967694, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "kmuCP", "2VzY"),
+ new _Row(27967693, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "cLc27", "ujAix"),
+ new _Row(27967692, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "UUHWcCtTe", "WAqv8AbdDPoSlh"),
+ new _Row(27967708, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "1hlglR0vEPdA3ZxqVI3E59targ", "9yl8QMmpLjCGS8F2ETvyCeo"),
+ };
+
+ using (var cn = new Microsoft.Data.SqlClient.SqlConnection(DataTestUtility.TCPConnectionString))
+ {
+ await cn.OpenAsync();
+
+ string tableName = DataTestUtility.GenerateObjectName();
+
+ try
+ {
+ using (var cmd = cn.CreateCommand())
+ {
+ cmd.CommandText = $"""
+ create table [{tableName}]
+ (
+ [Id] [int] NOT NULL,
+ [DocumentIdentificationId] [uniqueidentifier] NOT NULL,
+ [Name] [nvarchar](40) NULL,
+ [Value] [nvarchar](max) NULL
+ )
+ """;
+
+ await cmd.ExecuteNonQueryAsync();
+
+ cmd.CommandText = $"INSERT INTO [{tableName}] VALUES (@id,@docId,@name,@value)";
+ SqlParameter id = cmd.Parameters.AddWithValue("@id", 0);
+ SqlParameter docId = cmd.Parameters.AddWithValue("@docId", Guid.Empty);
+ SqlParameter name = cmd.Parameters.AddWithValue("@name", "");
+ SqlParameter value = cmd.Parameters.AddWithValue("@value", "");
+
+ foreach (var row in rows)
+ {
+ id.Value = row.Id;
+ docId.Value = row.DocumentIdentificationId;
+ name.Value = row.Name;
+ value.Value = row.Value == null ? DBNull.Value : row.Value;
+ await cmd.ExecuteNonQueryAsync();
+ }
+ }
+
+ int counter = 0;
+ while (counter < 10)
+ {
+ using (var cmd = cn.CreateCommand())
+ {
+ cmd.CommandText = $"SELECT [d].[Id], [d].[DocumentIdentificationId], [d].[Name], [d].[Value] FROM [{tableName}] AS [d]";
+ using (var reader = await cmd.ExecuteReaderAsync())
+ {
+ int row = 0;
+ while (await reader.ReadAsync())
+ {
+ int id = await reader.GetFieldValueAsync(0, default);
+ Guid docId = await reader.GetFieldValueAsync(1, default);
+ string name = await reader.GetFieldValueAsync(2, default);
+ string value = null;
+ if (!await reader.IsDBNullAsync(3, default))
+ {
+ value = await reader.GetFieldValueAsync(3, default);
+ }
+
+ Assert.Equal(id, rows[row].Id);
+ Assert.Equal(docId, rows[row].DocumentIdentificationId);
+ Assert.Equal(name, rows[row].Name);
+ Assert.Equal(value, rows[row].Value);
+ row += 1;
+ }
+ }
+ }
+ counter++;
+ }
+ }
+ finally
+ {
+ try
+ {
+ DataTestUtility.DropTable(cn, tableName);
+ }
+ catch
+ {
+ }
+ }
+ }
+ }
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs
index ee9c0ed4cb..506d8f81df 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs
@@ -13,13 +13,21 @@
using System.Threading.Tasks;
using System.Xml;
using Xunit;
+using Xunit.Abstractions;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
{
- public static class DataStreamTest
+ public class DataStreamTest
{
+ private readonly string _testName;
+
+ public DataStreamTest(ITestOutputHelper outputHelper)
+ {
+ _testName = DataTestUtility.CurrentTestName(outputHelper);
+ }
+
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))]
- public static void RunAllTestsForSingleServer_NP()
+ public void RunAllTestsForSingleServer_NP()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
@@ -33,7 +41,7 @@ public static void RunAllTestsForSingleServer_NP()
[ActiveIssue("5540")]
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
- public static void RunAllTestsForSingleServer_TCP()
+ public void RunAllTestsForSingleServer_TCP()
{
RunAllTestsForSingleServer(DataTestUtility.TCPConnectionString);
}
@@ -50,7 +58,7 @@ public static async Task AsyncMultiPacketStreamRead()
byte[] inputData = null;
byte[] outputData = null;
- string tableName = DataTestUtility.GetUniqueNameForSqlServer("data");
+ string tableName = DataTestUtility.GetLongName("data");
using (SqlConnection connection = new(connectionString))
{
@@ -152,7 +160,8 @@ IF OBJECT_ID('dbo.{tableName}', 'U') IS NOT NULL
return data;
}
- private static void RunAllTestsForSingleServer(string connectionString, bool usingNamePipes = false)
+ // @TODO: Split into separate tests!
+ private void RunAllTestsForSingleServer(string connectionString, bool usingNamePipes = false)
{
RowBuffer(connectionString);
InvalidRead(connectionString);
@@ -546,7 +555,7 @@ private static void RowBuffer(string connectionString)
private static void TimestampRead(string connectionString)
{
- string tempTable = DataTestUtility.GetUniqueNameForSqlServer("##Temp");
+ string tempTable = DataTestUtility.GetLongName("##Temp");
tempTable = tempTable.Replace('-', '_');
using (SqlConnection conn = new SqlConnection(connectionString))
@@ -1041,7 +1050,7 @@ private static void SequentialAccess(string connectionString)
private static void NumericRead(string connectionString)
{
- string tempTable = DataTestUtility.GetUniqueNameForSqlServer("##Temp");
+ string tempTable = DataTestUtility.GetLongName("##Temp");
tempTable = tempTable.Replace('-', '_');
using (SqlConnection conn = new SqlConnection(connectionString))
@@ -1871,8 +1880,8 @@ private static void StreamingBlobDataTypes(string connectionString)
private static void VariantCollationsTest(string connectionString)
{
- string dbName = DataTestUtility.GetUniqueName("JPN");
- string tableName = DataTestUtility.GetUniqueName("T");
+ string dbName = DataTestUtility.GetShortName("JPN");
+ string tableName = DataTestUtility.GetShortName("T");
using (SqlConnection connection = new SqlConnection(connectionString))
{
@@ -1911,56 +1920,61 @@ private static void VariantCollationsTest(string connectionString)
}
}
- private static void TestXEventsStreaming(string connectionString)
+ #nullable enable
+
+ private void TestXEventsStreaming(string connectionString)
{
// Create XEvent
- using (SqlConnection xEventManagementConnection = new SqlConnection(connectionString))
- using (DataTestUtility.XEventScope xEventScope = new DataTestUtility.XEventScope(xEventManagementConnection,
- "ADD EVENT sqlserver.user_event(ACTION(package0.event_sequence))",
- "ADD TARGET package0.ring_buffer"))
+ using SqlConnection xEventManagementConnection = new SqlConnection(connectionString);
+ xEventManagementConnection.Open();
+
+ using DataTestUtility.XEventScope xEventScope =
+ new DataTestUtility.XEventScope(
+ _testName,
+ xEventManagementConnection,
+ "ADD EVENT sqlserver.user_event(ACTION(package0.event_sequence))",
+ "ADD TARGET package0.ring_buffer");
+
+ string sessionName = xEventScope.SessionName;
+
+ Task.Factory.StartNew(() =>
{
- string sessionName = xEventScope.SessionName;
+ // Read XEvents
+ int streamXeventCount = 3;
+ using SqlConnection xEventsReadConnection = new SqlConnection(connectionString);
+ xEventsReadConnection.Open();
- Task.Factory.StartNew(() =>
- {
- // Read XEvents
- int streamXeventCount = 3;
- using (SqlConnection xEventsReadConnection = new SqlConnection(connectionString))
- {
- xEventsReadConnection.Open();
- string xEventDataStreamCommand = "USE master; " + @"select [type], [data] from sys.fn_MSxe_read_event_stream ('" + sessionName + "',0)";
- using (SqlCommand cmd = new SqlCommand(xEventDataStreamCommand, xEventsReadConnection))
- {
- SqlDataReader reader = cmd.ExecuteReader(System.Data.CommandBehavior.SequentialAccess);
- for (int i = 0; i < streamXeventCount && reader.Read(); i++)
- {
- int colType = reader.GetInt32(0);
- int cb = (int)reader.GetBytes(1, 0, null, 0, 0);
+ string xEventDataStreamCommand = "USE master; " + @"select [type], [data] from sys.fn_MSxe_read_event_stream ('" + sessionName + "',0)";
+ using SqlCommand cmd = new SqlCommand(xEventDataStreamCommand, xEventsReadConnection);
+ using SqlDataReader reader = cmd.ExecuteReader(System.Data.CommandBehavior.SequentialAccess);
- byte[] bytes = new byte[cb];
- long read = reader.GetBytes(1, 0, bytes, 0, cb);
+ for (int i = 0; i < streamXeventCount && reader.Read(); i++)
+ {
+ int colType = reader.GetInt32(0);
+ int cb = (int)reader.GetBytes(1, 0, null, 0, 0);
- // Don't send data on the first read because there is already data in the buffer.
- // Don't send data on the last iteration. We will not be reading that data.
- if (i == 0 || i == streamXeventCount - 1)
- continue;
+ byte[] bytes = new byte[cb];
+ long read = reader.GetBytes(1, 0, bytes, 0, cb);
- using (SqlConnection xEventWriteConnection = new SqlConnection(connectionString))
- {
- xEventWriteConnection.Open();
- string xEventWriteCommandText = @"exec sp_trace_generateevent 90, N'Test2'";
- using (SqlCommand xEventWriteCommand = new SqlCommand(xEventWriteCommandText, xEventWriteConnection))
- {
- xEventWriteCommand.ExecuteNonQuery();
- }
- }
- }
- }
+ // Don't send data on the first read because there is already data in the buffer.
+ // Don't send data on the last iteration. We will not be reading that data.
+ if (i == 0 || i == streamXeventCount - 1)
+ {
+ continue;
}
- }).Wait(10000);
- }
+
+ using SqlConnection xEventWriteConnection = new SqlConnection(connectionString);
+ xEventWriteConnection.Open();
+
+ string xEventWriteCommandText = @"exec sp_trace_generateevent 90, N'Test2'";
+ using SqlCommand xEventWriteCommand = new SqlCommand(xEventWriteCommandText, xEventWriteConnection);
+ xEventWriteCommand.ExecuteNonQuery();
+ }
+ }).Wait(10000);
}
+ #nullable disable
+
private static void TimeoutDuringReadAsyncWithClosedReaderTest(string connectionString)
{
// Create the proxy
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ExceptionTest/ConnectionExceptionTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ExceptionTest/ConnectionExceptionTest.cs
index 6ee0681a0d..c44ed97ed0 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ExceptionTest/ConnectionExceptionTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ExceptionTest/ConnectionExceptionTest.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
+using Microsoft.SqlServer.TDS.Servers;
using Xunit;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
@@ -23,8 +24,14 @@ public class ConnectionExceptionTest
[ConditionalFact(nameof(IsNotKerberos))]
public void TestConnectionStateWithErrorClass20()
{
- using TestTdsServer server = TestTdsServer.StartTestServer();
- using SqlConnection conn = new(server.ConnectionString);
+ using TdsServer server = new TdsServer();
+ server.Start();
+ using SqlConnection conn = new(
+ new SqlConnectionStringBuilder
+ {
+ DataSource = $"localhost,{server.EndPoint.Port}",
+ Encrypt = SqlConnectionEncryptOption.Optional
+ }.ConnectionString);
conn.Open();
SqlCommand cmd = conn.CreateCommand();
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs
index 24e5a277af..02133e0df4 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs
@@ -212,9 +212,9 @@ private static string GetSPNInfo(string dataSource, string inInstanceName)
string serverSPN = "";
MethodInfo getSqlServerSPNs = sniProxyObj.GetType().GetMethod("GetSqlServerSPNs", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, getSqlServerSPNsTypesArray, null);
- string[] result = (string[])getSqlServerSPNs.Invoke(sniProxyObj, new object[] { dataSrcInfo, serverSPN });
+ object resolvedSpns = getSqlServerSPNs.Invoke(sniProxyObj, new object[] { dataSrcInfo, serverSPN });
- string spnInfo = result[0];
+ string spnInfo = (string)resolvedSpns.GetType().GetProperty("Primary", BindingFlags.Instance | BindingFlags.Public).GetValue(resolvedSpns);
return spnInfo;
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonBulkCopyTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonBulkCopyTest.cs
index c92ba237b4..15f16b1f66 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonBulkCopyTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonBulkCopyTest.cs
@@ -7,18 +7,17 @@
using Newtonsoft.Json;
using Xunit.Abstractions;
using Xunit;
-using System.Collections;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.JsonTest
{
public class JsonBulkCopyTest
{
private readonly ITestOutputHelper _output;
- private static readonly string _generatedJsonFile = DataTestUtility.GenerateRandomCharacters("randomRecords");
- private static readonly string _outputFile = DataTestUtility.GenerateRandomCharacters("serverResults");
- private static readonly string _sourceTableName = DataTestUtility.GenerateObjectName();
- private static readonly string _destinationTableName = DataTestUtility.GenerateObjectName();
-
+ private static readonly string _generatedJsonFile = DataTestUtility.GetShortName("randomRecords");
+ private static readonly string _outputFile = DataTestUtility.GetShortName("serverResults");
+ private static readonly string _sourceTableName = DataTestUtility.GetShortName("jsonBulkCopySrcTable", true);
+ private static readonly string _destinationTableName = DataTestUtility.GetShortName("jsonBulkCopyDestTable", true);
+
public JsonBulkCopyTest(ITestOutputHelper output)
{
_output = output;
@@ -26,10 +25,10 @@ public JsonBulkCopyTest(ITestOutputHelper output)
public static IEnumerable JsonBulkCopyTestData()
{
- yield return new object[] { CommandBehavior.Default, false, 300, 100 };
- yield return new object[] { CommandBehavior.Default, true, 300, 100 };
- yield return new object[] { CommandBehavior.SequentialAccess, false, 300, 100 };
- yield return new object[] { CommandBehavior.SequentialAccess, true, 300, 100 };
+ yield return new object[] { CommandBehavior.Default, false, 30, 10 };
+ yield return new object[] { CommandBehavior.Default, true, 30, 10 };
+ yield return new object[] { CommandBehavior.SequentialAccess, false, 30, 10 };
+ yield return new object[] { CommandBehavior.SequentialAccess, true, 30, 10 };
}
private void PopulateData(int noOfRecords, int rows)
@@ -87,7 +86,7 @@ private void PrintJsonDataToFileAndCompare(SqlConnection connection)
try
{
DeleteFile(_outputFile);
- using (SqlCommand command = new SqlCommand("SELECT [data] FROM [" + _destinationTableName + "]", connection))
+ using (SqlCommand command = new SqlCommand("SELECT [data] FROM " + _destinationTableName, connection))
{
using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.SequentialAccess))
{
@@ -125,7 +124,7 @@ private async Task PrintJsonDataToFileAndCompareAsync(SqlConnection connection)
try
{
DeleteFile(_outputFile);
- using (SqlCommand command = new SqlCommand("SELECT [data] FROM [" + _destinationTableName + "]", connection))
+ using (SqlCommand command = new SqlCommand("SELECT [data] FROM " + _destinationTableName, connection))
{
using (SqlDataReader reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess))
{
@@ -159,7 +158,7 @@ private async Task PrintJsonDataToFileAndCompareAsync(SqlConnection connection)
private void StreamJsonFileToServer(SqlConnection connection)
{
- using (SqlCommand cmd = new SqlCommand("INSERT INTO [" + _sourceTableName + "] (data) VALUES (@jsondata)", connection))
+ using (SqlCommand cmd = new SqlCommand("INSERT INTO " + _sourceTableName + " (data) VALUES (@jsondata)", connection))
{
using (StreamReader jsonFile = File.OpenText(_generatedJsonFile))
{
@@ -171,7 +170,7 @@ private void StreamJsonFileToServer(SqlConnection connection)
private async Task StreamJsonFileToServerAsync(SqlConnection connection)
{
- using (SqlCommand cmd = new SqlCommand("INSERT INTO [" + _sourceTableName + "] (data) VALUES (@jsondata)", connection))
+ using (SqlCommand cmd = new SqlCommand("INSERT INTO " + _sourceTableName + " (data) VALUES (@jsondata)", connection))
{
using (StreamReader jsonFile = File.OpenText(_generatedJsonFile))
{
@@ -265,7 +264,7 @@ private async Task BulkCopyDataAsync(CommandBehavior cb, bool enableStraming, in
}
}
- [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))]
+ [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
[MemberData(
nameof(JsonBulkCopyTestData)
#if NETFRAMEWORK
@@ -289,7 +288,7 @@ public void TestJsonBulkCopy(CommandBehavior cb, bool enableStraming, int jsonAr
}
}
- [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))]
+ [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
[MemberData(
nameof(JsonBulkCopyTestData)
#if NETFRAMEWORK
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs
index a82fee1665..ed36457200 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs
@@ -19,11 +19,11 @@ public class JsonRecord
public string Name { get; set; }
}
- public class JsonStreamTest
+ public class JsonStreamTest
{
private readonly ITestOutputHelper _output;
- private static readonly string _jsonFile = "randomRecords.json";
- private static readonly string _outputFile = "serverRecords.json";
+ private static readonly string _jsonFile = DataTestUtility.GetShortName("randomRecords") + ".json";
+ private static readonly string _outputFile = DataTestUtility.GetShortName("serverRecords") + ".json";
public JsonStreamTest(ITestOutputHelper output)
{
@@ -49,7 +49,7 @@ private void GenerateJsonFile(int noOfRecords, string filename)
string json = JsonConvert.SerializeObject(records, Formatting.Indented);
File.WriteAllText(filename, json);
Assert.True(File.Exists(filename));
- _output.WriteLine("Generated JSON file "+filename);
+ _output.WriteLine("Generated JSON file " + filename);
}
private void CompareJsonFiles()
@@ -157,10 +157,10 @@ private void DeleteFile(string filename)
}
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public void TestJsonStreaming()
{
- GenerateJsonFile(10000, _jsonFile);
+ GenerateJsonFile(1000, _jsonFile);
using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString))
{
connection.Open();
@@ -173,10 +173,10 @@ public void TestJsonStreaming()
}
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public async Task TestJsonStreamingAsync()
{
- GenerateJsonFile(10000, _jsonFile);
+ GenerateJsonFile(1000, _jsonFile);
using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString))
{
await connection.OpenAsync();
@@ -190,4 +190,3 @@ public async Task TestJsonStreamingAsync()
}
}
}
-
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs
index ccf0c00919..55dffae12d 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs
@@ -22,7 +22,7 @@ public JsonTest(ITestOutputHelper output)
{
_output = output;
}
-
+
private static readonly string JsonDataString = "[{\"name\":\"Dave\",\"skills\":[\"Python\"]},{\"name\":\"Ron\",\"surname\":\"Peter\"}]";
private void ValidateRowsAffected(int rowsAffected)
@@ -73,7 +73,7 @@ private void ValidateNullJson(SqlDataReader reader)
}
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public void TestJsonWrite()
{
string tableName = DataTestUtility.GenerateObjectName();
@@ -137,7 +137,7 @@ public void TestJsonWrite()
}
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public async Task TestJsonWriteAsync()
{
string tableName = DataTestUtility.GenerateObjectName();
@@ -201,7 +201,7 @@ public async Task TestJsonWriteAsync()
}
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public void TestJsonRead()
{
string tableName = DataTestUtility.GenerateObjectName();
@@ -260,7 +260,7 @@ public void TestJsonRead()
}
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public async Task TestJsonReadAsync()
{
string tableName = DataTestUtility.GenerateObjectName();
@@ -319,7 +319,7 @@ public async Task TestJsonReadAsync()
}
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public void TestNullJson()
{
string tableName = DataTestUtility.GenerateObjectName();
@@ -350,7 +350,7 @@ public void TestNullJson()
DataTestUtility.DropTable(connection, tableName);
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public void TestJsonAPIs()
{
string tableName = DataTestUtility.GenerateObjectName();
@@ -398,7 +398,7 @@ public void TestJsonAPIs()
}
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public void TestJsonWithMARS()
{
string table1Name = DataTestUtility.GenerateObjectName();
@@ -454,7 +454,7 @@ public void TestJsonWithMARS()
}
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public void TestJsonSPParams()
{
string tableName = DataTestUtility.GenerateObjectName();
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MirroringTest/ConnectionOnMirroringTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MirroringTest/ConnectionOnMirroringTest.cs
index 93101d5d38..460d566a6d 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MirroringTest/ConnectionOnMirroringTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MirroringTest/ConnectionOnMirroringTest.cs
@@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Data;
using System.Threading;
+using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
@@ -28,17 +29,14 @@ public static void TestMultipleConnectionToMirroredServer()
builder.ConnectTimeout = 0;
TestWorker worker = new TestWorker(builder.ConnectionString);
- Thread childThread = new Thread(() => worker.TestMultipleConnection());
- childThread.Start();
+ Task childTask = Task.Factory.StartNew(() => worker.TestMultipleConnection(), TaskCreationOptions.LongRunning);
if (workerCompletedEvent.WaitOne(10000))
{
- childThread.Join();
+ childTask.Wait();
}
else
{
- // currently Thread.Abort() throws PlatformNotSupportedException in CoreFx.
- childThread.Interrupt();
throw new Exception("SqlConnection could not open and close successfully in timely manner. Possibly connection hangs.");
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTest.cs
index 31c232e3d0..20768e9329 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTest.cs
@@ -75,7 +75,7 @@ private static void TestSimpleParameter_Type(object paramValue, string expectedT
{
string tag = "TestSimpleParameter_Type";
DisplayHeader(tag, paramValue, expectedBaseTypeName);
- string procName = DataTestUtility.GetUniqueNameForSqlServer("paramProc1");
+ string procName = DataTestUtility.GetLongName("paramProc1");
try
{
using SqlConnection conn = new(s_connStr);
@@ -115,7 +115,7 @@ private static void TestSimpleParameter_Variant(object paramValue, string expect
{
string tag = "TestSimpleParameter_Variant";
DisplayHeader(tag, paramValue, expectedBaseTypeName);
- string procName = DataTestUtility.GetUniqueNameForSqlServer("paramProc2");
+ string procName = DataTestUtility.GetLongName("paramProc2");
try
{
using SqlConnection conn = new(s_connStr);
@@ -153,7 +153,7 @@ private static void TestSqlDataRecordParameterToTVP_Type(object paramValue, stri
{
string tag = "TestSqlDataRecordParameterToTVP_Type";
DisplayHeader(tag, paramValue, expectedBaseTypeName);
- string tvpTypeName = DataTestUtility.GetUniqueNameForSqlServer("tvpType");
+ string tvpTypeName = DataTestUtility.GetLongName("tvpType");
try
{
using SqlConnection conn = new(s_connStr);
@@ -200,7 +200,7 @@ private static void TestSqlDataRecordParameterToTVP_Variant(object paramValue, s
{
string tag = "TestSqlDataRecordParameterToTVP_Variant";
DisplayHeader(tag, paramValue, expectedBaseTypeName);
- string tvpTypeName = DataTestUtility.GetUniqueNameForSqlServer("tvpVariant");
+ string tvpTypeName = DataTestUtility.GetLongName("tvpVariant");
try
{
using SqlConnection conn = new(s_connStr);
@@ -245,7 +245,7 @@ private static void TestSqlDataReaderParameterToTVP_Type(object paramValue, stri
{
string tag = "TestSqlDataReaderParameterToTVP_Type";
DisplayHeader(tag, paramValue, expectedBaseTypeName);
- string tvpTypeName = DataTestUtility.GetUniqueNameForSqlServer("tvpType");
+ string tvpTypeName = DataTestUtility.GetLongName("tvpType");
try
{
using SqlConnection conn = new(s_connStr);
@@ -295,7 +295,7 @@ private static void TestSqlDataReaderParameterToTVP_Variant(object paramValue, s
{
string tag = "TestSqlDataReaderParameterToTVP_Variant";
DisplayHeader(tag, paramValue, expectedBaseTypeName);
- string tvpTypeName = DataTestUtility.GetUniqueNameForSqlServer("tvpVariant");
+ string tvpTypeName = DataTestUtility.GetLongName("tvpVariant");
try
{
using SqlConnection conn = new(s_connStr);
@@ -347,10 +347,10 @@ private static void TestSqlDataReader_TVP_Type(object paramValue, string expecte
{
string tag = "TestSqlDataReader_TVP_Type";
DisplayHeader(tag, paramValue, expectedBaseTypeName);
- string tvpTypeName = DataTestUtility.GetUniqueNameForSqlServer("tvpType");
- string InputTableName = DataTestUtility.GetUniqueNameForSqlServer("InputTable");
- string OutputTableName = DataTestUtility.GetUniqueNameForSqlServer("OutputTable");
- string ProcName = DataTestUtility.GetUniqueNameForSqlServer("spTVPProc");
+ string tvpTypeName = DataTestUtility.GetLongName("tvpType");
+ string InputTableName = DataTestUtility.GetLongName("InputTable");
+ string OutputTableName = DataTestUtility.GetLongName("OutputTable");
+ string ProcName = DataTestUtility.GetLongName("spTVPProc");
try
{
using SqlConnection conn = new(s_connStr);
@@ -428,10 +428,10 @@ private static void TestSqlDataReader_TVP_Variant(object paramValue, string expe
{
string tag = "TestSqlDataReader_TVP_Variant";
DisplayHeader(tag, paramValue, expectedBaseTypeName);
- string tvpTypeName = DataTestUtility.GetUniqueNameForSqlServer("tvpVariant_DRdrTVPVar");
- string InputTableName = DataTestUtility.GetUniqueNameForSqlServer("InputTable");
- string OutputTableName = DataTestUtility.GetUniqueNameForSqlServer("OutputTable");
- string ProcName = DataTestUtility.GetUniqueNameForSqlServer("spTVPProc_DRdrTVPVar");
+ string tvpTypeName = DataTestUtility.GetLongName("tvpVariant_DRdrTVPVar");
+ string InputTableName = DataTestUtility.GetLongName("InputTable");
+ string OutputTableName = DataTestUtility.GetLongName("OutputTable");
+ string ProcName = DataTestUtility.GetLongName("spTVPProc_DRdrTVPVar");
try
{
using SqlConnection conn = new(s_connStr);
@@ -512,8 +512,8 @@ private static void TestSimpleDataReader_Type(object paramValue, string expected
{
string tag = "TestSimpleDataReader_Type";
DisplayHeader(tag, paramValue, expectedBaseTypeName);
- string inputTable = DataTestUtility.GetUniqueNameForSqlServer("inputTable");
- string procName = DataTestUtility.GetUniqueNameForSqlServer("paramProc3");
+ string inputTable = DataTestUtility.GetLongName("inputTable");
+ string procName = DataTestUtility.GetLongName("paramProc3");
try
{
using SqlConnection conn = new(s_connStr);
@@ -568,8 +568,8 @@ private static void TestSimpleDataReader_Variant(object paramValue, string expec
{
string tag = "TestSimpleDataReader_Variant";
DisplayHeader(tag, paramValue, expectedBaseTypeName);
- string inputTable = DataTestUtility.GetUniqueNameForSqlServer("inputTable");
- string procName = DataTestUtility.GetUniqueNameForSqlServer("paramProc4");
+ string inputTable = DataTestUtility.GetLongName("inputTable");
+ string procName = DataTestUtility.GetLongName("paramProc4");
try
{
using SqlConnection conn = new(s_connStr);
@@ -624,8 +624,8 @@ private static void SqlBulkCopySqlDataReader_Type(object paramValue, string expe
{
string tag = "SqlBulkCopySqlDataReader_Type";
DisplayHeader(tag, paramValue, expectedBaseTypeName);
- string bulkCopySrcTableName = DataTestUtility.GetUniqueNameForSqlServer("bulkSrcTable");
- string bulkCopyTableName = DataTestUtility.GetUniqueNameForSqlServer("bulkDestTable");
+ string bulkCopySrcTableName = DataTestUtility.GetLongName("bulkSrcTable");
+ string bulkCopyTableName = DataTestUtility.GetLongName("bulkDestTable");
try
{
using SqlConnection conn = new(s_connStr);
@@ -698,8 +698,8 @@ private static void SqlBulkCopySqlDataReader_Variant(object paramValue, string e
{
string tag = "SqlBulkCopySqlDataReader_Variant";
DisplayHeader(tag, paramValue, expectedBaseTypeName);
- string bulkCopySrcTableName = DataTestUtility.GetUniqueNameForSqlServer("bulkSrcTable");
- string bulkCopyTableName = DataTestUtility.GetUniqueNameForSqlServer("bulkDestTable");
+ string bulkCopySrcTableName = DataTestUtility.GetLongName("bulkSrcTable");
+ string bulkCopyTableName = DataTestUtility.GetLongName("bulkDestTable");
try
{
using SqlConnection conn = new(s_connStr);
@@ -776,7 +776,7 @@ private static void SqlBulkCopyDataTable_Type(object paramValue, string expected
{
string tag = "SqlBulkCopyDataTable_Type";
DisplayHeader(tag, paramValue, expectedBaseTypeName);
- string bulkCopyTableName = DataTestUtility.GetUniqueNameForSqlServer("bulkDestType");
+ string bulkCopyTableName = DataTestUtility.GetLongName("bulkDestType");
try
{
using SqlConnection conn = new(s_connStr);
@@ -836,7 +836,7 @@ private static void SqlBulkCopyDataTable_Variant(object paramValue, string expec
{
string tag = "SqlBulkCopyDataTable_Variant";
DisplayHeader(tag, paramValue, expectedBaseTypeName);
- string bulkCopyTableName = DataTestUtility.GetUniqueNameForSqlServer("bulkDestVariant");
+ string bulkCopyTableName = DataTestUtility.GetLongName("bulkDestVariant");
try
{
using SqlConnection conn = new(s_connStr);
@@ -886,7 +886,7 @@ private static void SqlBulkCopyDataRow_Type(object paramValue, string expectedTy
{
string tag = "SqlBulkCopyDataRow_Type";
DisplayHeader(tag, paramValue, expectedBaseTypeName);
- string bulkCopyTableName = DataTestUtility.GetUniqueNameForSqlServer("bulkDestType");
+ string bulkCopyTableName = DataTestUtility.GetLongName("bulkDestType");
try
{
using SqlConnection conn = new(s_connStr);
@@ -941,7 +941,7 @@ private static void SqlBulkCopyDataRow_Variant(object paramValue, string expecte
{
string tag = "SqlBulkCopyDataRow_Variant";
DisplayHeader(tag, paramValue, expectedBaseTypeName);
- string bulkCopyTableName = DataTestUtility.GetUniqueNameForSqlServer("bulkDestVariant");
+ string bulkCopyTableName = DataTestUtility.GetLongName("bulkDestVariant");
try
{
using SqlConnection conn = new(s_connStr);
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs
index d7603ec807..2f9a2ecb1c 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs
@@ -8,6 +8,7 @@
using System.Data;
using System.Data.SqlTypes;
using System.Threading;
+using System.Threading.Tasks;
using Xunit;
using System.Globalization;
@@ -16,6 +17,8 @@
using Microsoft.Data.SqlClient.Server;
#endif
+using Microsoft.Data.SqlClient.Tests.Common;
+
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
{
public static class ParametersTest
@@ -117,13 +120,13 @@ public static void CodeCoverageSqlClient()
public static void Test_Copy_SqlParameter()
{
using var conn = new SqlConnection(s_connString);
- string cTableName = DataTestUtility.GetUniqueNameForSqlServer("#tmp");
+ string cTableName = DataTestUtility.GetLongName("#tmp");
try
{
// Create tmp table
var sCreateTable = "IF NOT EXISTS(";
- sCreateTable += $"SELECT * FROM sysobjects WHERE name= '{ cTableName }' and xtype = 'U')";
- sCreateTable += $"CREATE TABLE { cTableName }( BinValue binary(16) null)";
+ sCreateTable += $"SELECT * FROM sysobjects WHERE name= '{cTableName}' and xtype = 'U')";
+ sCreateTable += $"CREATE TABLE {cTableName}( BinValue binary(16) null)";
conn.Open();
var cmd = new SqlCommand(sCreateTable, conn);
@@ -140,7 +143,7 @@ public static void Test_Copy_SqlParameter()
UpdatedRowSource = UpdateRowSource.None,
Connection = conn,
- CommandText = $"INSERT { cTableName } (BinValue) "
+ CommandText = $"INSERT {cTableName} (BinValue) "
};
cmdInsert.CommandText += "Values(@BinValue)";
cmdInsert.Parameters.Add("@BinValue", SqlDbType.Binary, 16, "SourceBinValue");
@@ -259,9 +262,9 @@ public static void TestParametersWithDatatablesTVPInsert()
};
using SqlConnection connection = new(builder.ConnectionString);
- string tableName = DataTestUtility.GetUniqueNameForSqlServer("Table");
- string procName = DataTestUtility.GetUniqueNameForSqlServer("Proc");
- string typeName = DataTestUtility.GetUniqueName("Type");
+ string tableName = DataTestUtility.GetLongName("Table");
+ string procName = DataTestUtility.GetLongName("Proc");
+ string typeName = DataTestUtility.GetShortName("Type");
try
{
connection.Open();
@@ -338,10 +341,10 @@ public static void TestParametersWithSqlRecordsTVPInsert()
record1,
record2,
};
-
+
using SqlConnection connection = new(builder.ConnectionString);
- string procName = DataTestUtility.GetUniqueNameForSqlServer("Proc");
- string typeName = DataTestUtility.GetUniqueName("Type");
+ string procName = DataTestUtility.GetLongName("Proc");
+ string typeName = DataTestUtility.GetShortName("Type");
try
{
connection.Open();
@@ -400,8 +403,8 @@ @newRoads as {typeName} READONLY
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
public static void TestDateOnlyTVPDataTable_CommandSP()
{
- string tableTypeName = "[dbo]." + DataTestUtility.GetUniqueNameForSqlServer("UDTTTestDateOnlyTVP");
- string spName = DataTestUtility.GetUniqueNameForSqlServer("spTestDateOnlyTVP");
+ string tableTypeName = "[dbo]." + DataTestUtility.GetLongName("UDTTTestDateOnlyTVP");
+ string spName = DataTestUtility.GetLongName("spTestDateOnlyTVP");
SqlConnection connection = new(s_connString);
try
{
@@ -418,7 +421,7 @@ public static void TestDateOnlyTVPDataTable_CommandSP()
{
cmd.CommandText = spName;
cmd.CommandType = CommandType.StoredProcedure;
-
+
DataTable dtTest = new();
dtTest.Columns.Add(new DataColumn("DateColumn", typeof(DateOnly)));
dtTest.Columns.Add(new DataColumn("TimeColumn", typeof(TimeOnly)));
@@ -448,8 +451,8 @@ public static void TestDateOnlyTVPDataTable_CommandSP()
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
public static void TestDateOnlyTVPSqlDataRecord_CommandSP()
{
- string tableTypeName = "[dbo]." + DataTestUtility.GetUniqueNameForSqlServer("UDTTTestDateOnlySqlDataRecordTVP");
- string spName = DataTestUtility.GetUniqueNameForSqlServer("spTestDateOnlySqlDataRecordTVP");
+ string tableTypeName = "[dbo]." + DataTestUtility.GetLongName("UDTTTestDateOnlySqlDataRecordTVP");
+ string spName = DataTestUtility.GetLongName("spTestDateOnlySqlDataRecordTVP");
SqlConnection connection = new(s_connString);
try
{
@@ -569,7 +572,9 @@ public static void SqlDecimalConvertToDecimal_TestOutOfRange(string sqlDecimalVa
[ClassData(typeof(ConnectionStringsProvider))]
public static void TestScaledDecimalParameter_CommandInsert(string connectionString, bool truncateScaledDecimal)
{
- string tableName = DataTestUtility.GetUniqueNameForSqlServer("TestDecimalParameterCMD");
+ using LocalAppContextSwitchesHelper appContextSwitchesHelper = new();
+
+ string tableName = DataTestUtility.GetLongName("TestDecimalParameterCMD");
using SqlConnection connection = InitialDatabaseTable(connectionString, tableName);
try
{
@@ -601,7 +606,9 @@ public static void TestScaledDecimalParameter_CommandInsert(string connectionStr
[ClassData(typeof(ConnectionStringsProvider))]
public static void TestScaledDecimalParameter_BulkCopy(string connectionString, bool truncateScaledDecimal)
{
- string tableName = DataTestUtility.GetUniqueNameForSqlServer("TestDecimalParameterBC");
+ using LocalAppContextSwitchesHelper appContextSwitchesHelper = new();
+
+ string tableName = DataTestUtility.GetLongName("TestDecimalParameterBC");
using SqlConnection connection = InitialDatabaseTable(connectionString, tableName);
try
{
@@ -635,9 +642,11 @@ public static void TestScaledDecimalParameter_BulkCopy(string connectionString,
[ClassData(typeof(ConnectionStringsProvider))]
public static void TestScaledDecimalTVP_CommandSP(string connectionString, bool truncateScaledDecimal)
{
- string tableName = DataTestUtility.GetUniqueNameForSqlServer("TestDecimalParameterBC");
- string tableTypeName = DataTestUtility.GetUniqueNameForSqlServer("UDTTTestDecimalParameterBC");
- string spName = DataTestUtility.GetUniqueNameForSqlServer("spTestDecimalParameterBC");
+ using LocalAppContextSwitchesHelper appContextSwitchesHelper = new();
+
+ string tableName = DataTestUtility.GetLongName("TestDecimalParameterBC");
+ string tableTypeName = DataTestUtility.GetLongName("UDTTTestDecimalParameterBC");
+ string spName = DataTestUtility.GetLongName("spTestDecimalParameterBC");
using SqlConnection connection = InitialDatabaseUDTT(connectionString, tableName, tableTypeName, spName);
try
{
@@ -922,7 +931,7 @@ private static void EnableOptimizedParameterBinding_ReturnSucceeds()
{
int firstInput = 12;
- string sprocName = DataTestUtility.GetUniqueName("P");
+ string sprocName = DataTestUtility.GetShortName("P");
// input, output
string createSprocQuery =
"CREATE PROCEDURE " + sprocName + " @in int " +
@@ -957,30 +966,19 @@ private static void EnableOptimizedParameterBinding_ReturnSucceeds()
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
public static void ClosedConnection_SqlParameterValueTest()
{
- var threads = new List();
- for (int i = 0; i < 100; i++)
+ var tasks = new Task[100];
+ for (int i = 0; i < tasks.Length; i++)
{
- var t = new Thread(() =>
+ var t = Task.Factory.StartNew(() =>
{
for (int j = 0; j < 1000; j++)
{
- try
- {
- RunParameterTest();
- }
- catch (Exception e)
- {
- Assert.Fail($"Unexpected exception occurred: {e.Message}");
- }
+ RunParameterTest();
}
- });
- t.Start();
- threads.Add(t);
- }
- for (int i = 0; i < threads.Count; i++)
- {
- threads[i].Join();
+ }, TaskCreationOptions.LongRunning);
+ tasks[i] = t;
}
+ Task.WaitAll(tasks);
}
private static void RunParameterTest()
@@ -998,7 +996,7 @@ private static void RunParameterTest()
cm.Parameters.Add(new SqlParameter("@id2", SqlDbType.UniqueIdentifier) { Direction = ParameterDirection.Output });
try
{
- System.Threading.Tasks.Task task = cm.ExecuteNonQueryAsync(cancellationToken.Token);
+ Task task = cm.ExecuteNonQueryAsync(cancellationToken.Token);
task.Wait();
}
catch (Exception)
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlAdapterUpdateBatch.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlAdapterUpdateBatch.cs
index 7f383e8201..aa59bc319c 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlAdapterUpdateBatch.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlAdapterUpdateBatch.cs
@@ -15,7 +15,7 @@ public class SqlAdapterUpdateBatch
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
public void SqlAdapterTest()
{
- string tableName = DataTestUtility.GetUniqueNameForSqlServer("Adapter");
+ string tableName = DataTestUtility.GetLongName("Adapter");
string tableNameNoBrackets = tableName.Substring(1, tableName.Length - 2);
try
{
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParam.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParam.cs
index 2d11274191..e1592825b1 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParam.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParam.cs
@@ -108,7 +108,7 @@ private static void SendVariantParam(object paramValue, string expectedTypeName,
///
private static void SendVariantBulkCopy(object paramValue, string expectedTypeName, string expectedBaseTypeName)
{
- string bulkCopyTableName = DataTestUtility.GetUniqueNameForSqlServer("bulkDest");
+ string bulkCopyTableName = DataTestUtility.GetLongName("bulkDest");
// Fetch reader using type.
using SqlDataReader dr = GetReaderForVariant(paramValue, false);
@@ -194,7 +194,7 @@ private static void SendVariantBulkCopy(object paramValue, string expectedTypeNa
///
private static void SendVariantTvp(object paramValue, string expectedTypeName, string expectedBaseTypeName)
{
- string tvpTypeName = DataTestUtility.GetUniqueNameForSqlServer("tvpVariant");
+ string tvpTypeName = DataTestUtility.GetLongName("tvpVariant");
using SqlConnection connTvp = new(s_connStr);
connTvp.Open();
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RandomStressTest/RandomStressTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RandomStressTest/RandomStressTest.cs
index 86b5438a1a..79bf05a7f8 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RandomStressTest/RandomStressTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RandomStressTest/RandomStressTest.cs
@@ -8,6 +8,7 @@
using System.Diagnostics;
using System.Text;
using System.Threading;
+using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
@@ -70,8 +71,7 @@ public void TestMain()
{
for (int tcount = 0; tcount < ThreadCountDefault; tcount++)
{
- Thread t = new Thread(TestThread);
- t.Start();
+ _ = Task.Factory.StartNew(TestThread, TaskCreationOptions.LongRunning);
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs
index 7c590ebe05..21165e7624 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs
@@ -268,7 +268,7 @@ public void RetryExecuteUnauthorizedSqlStatementDML(string cnnString, SqlRetryLo
public void DropDatabaseWithActiveConnection(string cnnString, SqlRetryLogicBaseProvider provider)
{
int currentRetries = 0;
- string database = DataTestUtility.GetUniqueNameForSqlServer($"RetryLogic_{provider.RetryLogic.RetryIntervalEnumerator.GetType().Name}", false);
+ string database = DataTestUtility.GetLongName($"RetryLogic_{provider.RetryLogic.RetryIntervalEnumerator.GetType().Name}", false);
var builder = new SqlConnectionStringBuilder(cnnString)
{
InitialCatalog = database,
@@ -330,7 +330,7 @@ public void DropDatabaseWithActiveConnection(string cnnString, SqlRetryLogicBase
public void UpdateALockedTable(string cnnString, SqlRetryLogicBaseProvider provider)
{
int currentRetries = 0;
- string tableName = DataTestUtility.GetUniqueNameForSqlServer("Region");
+ string tableName = DataTestUtility.GetLongName("Region");
string fieldName = "RegionDescription";
using (var cnn1 = new SqlConnection(cnnString))
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs
index 61a1b07c5e..e5ed05e09f 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs
@@ -58,7 +58,7 @@ public void ConnectionCancelRetryOpenInvalidCatalog(string cnnString, SqlRetryLo
public void CreateDatabaseWhileTryingToConnect(string cnnString, SqlRetryLogicBaseProvider provider)
{
int currentRetries = 0;
- string database = DataTestUtility.GetUniqueNameForSqlServer($"RetryLogic_{provider.RetryLogic.RetryIntervalEnumerator.GetType().Name}", false);
+ string database = DataTestUtility.GetLongName($"RetryLogic_{provider.RetryLogic.RetryIntervalEnumerator.GetType().Name}", false);
var builder = new SqlConnectionStringBuilder(cnnString)
{
InitialCatalog = database,
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs
index 72bab47869..a845710d50 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs
@@ -41,7 +41,7 @@ public static void RunTest()
private static SqlDecimal BulkCopySqlDecimalToTable(SqlDecimal decimalValue, int sourcePrecision, int sourceScale, int targetPrecision, int targetScale)
{
- string tableName = DataTestUtility.GetUniqueNameForSqlServer("Table");
+ string tableName = DataTestUtility.GetLongName("Table");
string connectionString = DataTestUtility.TCPConnectionString;
SqlDecimal resultValue;
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AzureDistributedTransaction.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AzureDistributedTransaction.cs
index 823bc50a9d..2a853d7ed4 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AzureDistributedTransaction.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AzureDistributedTransaction.cs
@@ -11,7 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests
public class AzureDistributedTransaction
{
private static readonly string s_connectionString = DataTestUtility.TCPConnectionString;
- private static readonly string s_tableName = DataTestUtility.GetUniqueNameForSqlServer("Azure");
+ private static readonly string s_tableName = DataTestUtility.GetLongName("Azure");
private static readonly string s_createTableCmd = $"CREATE TABLE {s_tableName} (NAME NVARCHAR(40), AGE INT)";
private static readonly string s_sqlBulkCopyCmd = "SELECT * FROM(VALUES ('Fuller', 33), ('Davon', 49)) AS q (FirstName, Age)";
private static readonly int s_commandTimeout = 30;
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs
index 4d1dd14cfb..beb8df7992 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs
@@ -52,8 +52,6 @@ public static void Test(string srcConstr, string dstConstr, string dstTable)
Assert.True(0 < (long)stats["BytesReceived"], "BytesReceived is non-positive.");
Assert.True(0 < (long)stats["BytesSent"], "BytesSent is non-positive.");
- Assert.True((long)stats["ConnectionTime"] >= (long)stats["ExecutionTime"], "Connection Time is less than Execution Time.");
- Assert.True((long)stats["ExecutionTime"] >= (long)stats["NetworkServerTime"], "Execution Time is less than Network Server Time.");
DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["UnpreparedExecs"], "Non-zero UnpreparedExecs value: " + (long)stats["UnpreparedExecs"]);
DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["PreparedExecs"], "Non-zero PreparedExecs value: " + (long)stats["PreparedExecs"]);
DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["Prepares"], "Non-zero Prepares value: " + (long)stats["Prepares"]);
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWidenNullInexactNumerics.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWidenNullInexactNumerics.cs
index 5ccda71fb9..f961521233 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWidenNullInexactNumerics.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWidenNullInexactNumerics.cs
@@ -12,8 +12,8 @@ public class CopyWidenNullInexactNumerics
{
public static void Test(string sourceDatabaseConnectionString, string destinationDatabaseConnectionString)
{
- string sourceTableName = DataTestUtility.GetUniqueNameForSqlServer("BCP_SRC");
- string destTableName = DataTestUtility.GetUniqueNameForSqlServer("BCP_DST");
+ string sourceTableName = DataTestUtility.GetLongName("BCP_SRC");
+ string destTableName = DataTestUtility.GetLongName("BCP_DST");
// this test copies float and real inexact numeric types into decimal targets using bulk copy to check that the widening of the type succeeds.
using (var sourceConnection = new SqlConnection(sourceDatabaseConnectionString))
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/DataConversionErrorMessageTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/DataConversionErrorMessageTest.cs
index 4c3d594ad1..4a722dd409 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/DataConversionErrorMessageTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/DataConversionErrorMessageTest.cs
@@ -28,7 +28,7 @@ public InitialDatabase()
srcConstr = DataTestUtility.TCPConnectionString;
Connection = new SqlConnection(srcConstr);
- TableName = DataTestUtility.GetUniqueNameForSqlServer("SqlBulkCopyTest_CopyStringToIntTest_");
+ TableName = DataTestUtility.GetLongName("SqlBulkCopyTest_CopyStringToIntTest_");
InitialTable(Connection, TableName);
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TestBulkCopyWithUTF8.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TestBulkCopyWithUTF8.cs
index dc3779b4dc..5b7112476d 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TestBulkCopyWithUTF8.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TestBulkCopyWithUTF8.cs
@@ -15,8 +15,8 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests
///
public sealed class TestBulkCopyWithUtf8 : IDisposable
{
- private static string s_sourceTable = DataTestUtility.GetUniqueName("SourceTableForUTF8Data");
- private static string s_destinationTable = DataTestUtility.GetUniqueName("DestinationTableForUTF8Data");
+ private static string s_sourceTable = DataTestUtility.GetShortName("SourceTableForUTF8Data");
+ private static string s_destinationTable = DataTestUtility.GetShortName("DestinationTableForUTF8Data");
private static string s_testValue = "test";
private static byte[] s_testValueInUtf8Bytes = new byte[] { 0x74, 0x65, 0x73, 0x74 };
private static readonly string s_insertQuery = $"INSERT INTO {s_sourceTable} VALUES('{s_testValue}')";
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/WriteToServerTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/WriteToServerTest.cs
index 343a7bcfe2..20f63a2e0a 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/WriteToServerTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/WriteToServerTest.cs
@@ -12,8 +12,8 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests
public class WriteToServerTest
{
private readonly string _connectionString = null;
- private readonly string _tableName1 = DataTestUtility.GetUniqueName("Bulk1");
- private readonly string _tableName2 = DataTestUtility.GetUniqueName("Bulk2");
+ private readonly string _tableName1 = DataTestUtility.GetShortName("Bulk1");
+ private readonly string _tableName2 = DataTestUtility.GetShortName("Bulk2");
public WriteToServerTest()
{
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCompletedTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCompletedTest.cs
index 8e38bee7c0..21ff771ac0 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCompletedTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCompletedTest.cs
@@ -11,7 +11,7 @@ public static class SqlCommandCompletedTest
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
public static void VerifyStatmentCompletedCalled()
{
- string tableName = DataTestUtility.GetUniqueNameForSqlServer("stmt");
+ string tableName = DataTestUtility.GetLongName("stmt");
using (var conn = new SqlConnection(s_connStr))
using (var cmd = conn.CreateCommand())
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandSetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandSetTest.cs
index 26b11055c2..7f28a4a09a 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandSetTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandSetTest.cs
@@ -15,8 +15,8 @@ public class SqlCommandSetTest
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
public void TestByteArrayParameters()
{
- string tableName = DataTestUtility.GetUniqueNameForSqlServer("CMD");
- string procName = DataTestUtility.GetUniqueNameForSqlServer("CMD");
+ string tableName = DataTestUtility.GetLongName("CMD");
+ string procName = DataTestUtility.GetLongName("CMD");
byte[] bArray = new byte[] { 1, 2, 3 };
using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString))
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlFileStreamTest/SqlFileStreamTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlFileStreamTest/SqlFileStreamTest.cs
index 742a800bb9..9cba46959f 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlFileStreamTest/SqlFileStreamTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlFileStreamTest/SqlFileStreamTest.cs
@@ -221,7 +221,7 @@ private static string SetupFileStreamDB()
fileStreamDir += "\\";
}
- string dbName = DataTestUtility.GetUniqueName("FS", false);
+ string dbName = DataTestUtility.GetShortName("FS", false);
string createDBQuery = @$"CREATE DATABASE [{dbName}]
ON PRIMARY
(NAME = PhotoLibrary_data,
@@ -266,7 +266,7 @@ private static void DropFileStreamDb(string connString)
private static string SetupTable(string connString)
{
// Generate random table name
- string tempTable = DataTestUtility.GetUniqueNameForSqlServer("fs");
+ string tempTable = DataTestUtility.GetLongName("fs");
// Create table
string createTable = $"CREATE TABLE {tempTable} (EmployeeId INT NOT NULL PRIMARY KEY, Photo VARBINARY(MAX) FILESTREAM NULL, RowGuid UNIQUEIDENTIFIER NOT NULL ROWGUIDCOL UNIQUE DEFAULT NEWID() ) ";
ExecuteNonQueryCommand(createTable, connString);
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/SqlServerTypesTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/SqlServerTypesTest.cs
index 0fd8f22daf..67a5cf2748 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/SqlServerTypesTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/SqlServerTypesTest.cs
@@ -408,7 +408,7 @@ private static string GetUdtName(Type udtClrType)
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
public static void TestSqlServerTypesInsertAndRead()
{
- string tableName = DataTestUtility.GetUniqueNameForSqlServer("Type");
+ string tableName = DataTestUtility.GetLongName("Type");
string allTypesSQL = @$"
if not exists (select * from sysobjects where name='{tableName}' and xtype='U')
Begin
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtBulkCopyTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtBulkCopyTest.cs
index 8adf6c7bb5..469c895a61 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtBulkCopyTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtBulkCopyTest.cs
@@ -18,9 +18,9 @@ public void RunCopyTest()
_connStr = (new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { InitialCatalog = DataTestUtility.UdtTestDbName }).ConnectionString;
SqlConnection conn = new SqlConnection(_connStr);
- string cities = DataTestUtility.GetUniqueNameForSqlServer("UdtBulkCopy_cities");
- string customers = DataTestUtility.GetUniqueNameForSqlServer("UdtBulkCopy_customers");
- string circles = DataTestUtility.GetUniqueNameForSqlServer("UdtBulkCopy_circles");
+ string cities = DataTestUtility.GetLongName("UdtBulkCopy_cities");
+ string customers = DataTestUtility.GetLongName("UdtBulkCopy_customers");
+ string circles = DataTestUtility.GetLongName("UdtBulkCopy_circles");
conn.Open();
try
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs
index 59896086f4..74e6aaa277 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs
@@ -32,7 +32,7 @@ public DateTimeOffsetVariableScale(DateTimeOffset dateTimeOffset, int scale)
public class UdtDateTimeOffsetTest
{
private readonly string _connectionString = null;
- private readonly string _udtTableType = DataTestUtility.GetUniqueNameForSqlServer("DataTimeOffsetTableType");
+ private readonly string _udtTableType = DataTestUtility.GetLongName("DataTimeOffsetTableType");
private readonly ITestOutputHelper _testOutputHelper;
public UdtDateTimeOffsetTest(ITestOutputHelper testOutputHelper)
@@ -87,7 +87,7 @@ public void DateTimeOffsetAllScalesTestShouldSucceed()
for (int scale = fromScale; scale <= toScale; scale++)
{
- string tvpTypeName = DataTestUtility.GetUniqueNameForSqlServer("tvpType"); // Need a unique name per scale, else we get errors. See https://github.com/dotnet/SqlClient/issues/3011
+ string tvpTypeName = DataTestUtility.GetLongName("tvpType"); // Need a unique name per scale, else we get errors. See https://github.com/dotnet/SqlClient/issues/3011
DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 23, 59, 59, TimeSpan.Zero);
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs
index 16d48d7c37..85dbf99b33 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs
@@ -84,8 +84,8 @@ public void UDTParams_Binary()
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsUdtTestDatabasePresent), nameof(DataTestUtility.AreConnStringsSetup))]
public void UDTParams_Invalid2()
{
- string spInsertCustomer = DataTestUtility.GetUniqueNameForSqlServer("spUdtTest2_InsertCustomer");
- string tableName = DataTestUtility.GetUniqueNameForSqlServer("UdtTest2");
+ string spInsertCustomer = DataTestUtility.GetLongName("spUdtTest2_InsertCustomer");
+ string tableName = DataTestUtility.GetLongName("UdtTest2");
using (SqlConnection conn = new SqlConnection(_connStr))
using (SqlCommand cmd = conn.CreateCommand())
@@ -143,8 +143,8 @@ public void UDTParams_Invalid()
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsUdtTestDatabasePresent), nameof(DataTestUtility.AreConnStringsSetup))]
public void UDTParams_TypedNull()
{
- string spInsertCustomer = DataTestUtility.GetUniqueNameForSqlServer("spUdtTest2_InsertCustomer");
- string tableName = DataTestUtility.GetUniqueNameForSqlServer("UdtTest2_Customer");
+ string spInsertCustomer = DataTestUtility.GetLongName("spUdtTest2_InsertCustomer");
+ string tableName = DataTestUtility.GetLongName("UdtTest2_Customer");
using (SqlConnection conn = new SqlConnection(_connStr))
using (SqlCommand cmd = conn.CreateCommand())
@@ -188,8 +188,8 @@ public void UDTParams_TypedNull()
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsUdtTestDatabasePresent), nameof(DataTestUtility.AreConnStringsSetup))]
public void UDTParams_NullInput()
{
- string spInsertCustomer = DataTestUtility.GetUniqueNameForSqlServer("spUdtTest2_InsertCustomer");
- string tableName = DataTestUtility.GetUniqueNameForSqlServer("UdtTest2_Customer");
+ string spInsertCustomer = DataTestUtility.GetLongName("spUdtTest2_InsertCustomer");
+ string tableName = DataTestUtility.GetLongName("UdtTest2_Customer");
using (SqlConnection conn = new SqlConnection(_connStr))
using (SqlCommand cmd = conn.CreateCommand())
@@ -232,8 +232,8 @@ public void UDTParams_NullInput()
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsUdtTestDatabasePresent), nameof(DataTestUtility.AreConnStringsSetup))]
public void UDTParams_InputOutput()
{
- string spInsertCity = DataTestUtility.GetUniqueNameForSqlServer("spUdtTest2_InsertCity");
- string tableName = DataTestUtility.GetUniqueNameForSqlServer("UdtTest2");
+ string spInsertCity = DataTestUtility.GetLongName("spUdtTest2_InsertCity");
+ string tableName = DataTestUtility.GetLongName("UdtTest2");
using (SqlConnection conn = new SqlConnection(_connStr))
{
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs
index effecb35b3..41f81b12e3 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs
@@ -37,7 +37,7 @@ public static void CheckSupportUtf8ConnectionProperty()
public static void UTF8databaseTest()
{
const string letters = @"!\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f€\u0081‚ƒ„…†‡ˆ‰Š‹Œ\u008dŽ\u008f\u0090‘’“”•–—˜™š›œ\u009džŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ";
- string dbName = DataTestUtility.GetUniqueNameForSqlServer("UTF8databaseTest", false);
+ string dbName = DataTestUtility.GetLongName("UTF8databaseTest", false);
string tblName = "Table1";
SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString);
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs
index 8d205cfc9c..d905e15bc5 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs
@@ -18,37 +18,38 @@ public static class VectorFloat32TestData
{
public const int VectorHeaderSize = 8;
public static float[] testData = new float[] { 1.1f, 2.2f, 3.3f };
- public static int sizeInbytes = VectorHeaderSize + testData.Length * sizeof(float);
public static int vectorColumnLength = testData.Length;
+ // Incorrect size for SqlParameter.Size
+ public static int IncorrectParamSize = 3234;
public static IEnumerable GetVectorFloat32TestData()
{
// Pattern 1-4 with SqlVector(values: testData)
- yield return new object[] { 1, new SqlVector(testData), testData, sizeInbytes, vectorColumnLength };
- yield return new object[] { 2, new SqlVector(testData), testData, sizeInbytes, vectorColumnLength };
- yield return new object[] { 3, new SqlVector(testData), testData, sizeInbytes, vectorColumnLength };
- yield return new object[] { 4, new SqlVector(testData), testData, sizeInbytes, vectorColumnLength };
+ yield return new object[] { 1, new SqlVector(testData), testData, vectorColumnLength };
+ yield return new object[] { 2, new SqlVector(testData), testData, vectorColumnLength };
+ yield return new object[] { 3, new SqlVector(testData), testData, vectorColumnLength };
+ yield return new object[] { 4, new SqlVector(testData), testData, vectorColumnLength };
// Pattern 1-4 with SqlVector(n)
- yield return new object[] { 1, new SqlVector(vectorColumnLength), Array.Empty(), sizeInbytes, vectorColumnLength };
- yield return new object[] { 2, new SqlVector(vectorColumnLength), Array.Empty(), sizeInbytes, vectorColumnLength };
- yield return new object[] { 3, new SqlVector(vectorColumnLength), Array.Empty(), sizeInbytes, vectorColumnLength };
- yield return new object[] { 4, new SqlVector(vectorColumnLength), Array.Empty(), sizeInbytes, vectorColumnLength };
+ yield return new object[] { 1, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength };
+ yield return new object[] { 2, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength };
+ yield return new object[] { 3, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength };
+ yield return new object[] { 4, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength };
// Pattern 1-4 with DBNull
- yield return new object[] { 1, DBNull.Value, Array.Empty(), sizeInbytes, vectorColumnLength };
- yield return new object[] { 2, DBNull.Value, Array.Empty(), sizeInbytes, vectorColumnLength };
- yield return new object[] { 3, DBNull.Value, Array.Empty(), sizeInbytes, vectorColumnLength };
- yield return new object[] { 4, DBNull.Value, Array.Empty(), sizeInbytes, vectorColumnLength };
+ yield return new object[] { 1, DBNull.Value, Array.Empty(), vectorColumnLength };
+ yield return new object[] { 2, DBNull.Value, Array.Empty(), vectorColumnLength };
+ yield return new object[] { 3, DBNull.Value, Array.Empty(), vectorColumnLength };
+ yield return new object[] { 4, DBNull.Value, Array.Empty(), vectorColumnLength };
// Pattern 1-4 with SqlVector.Null
- yield return new object[] { 1, SqlVector.Null, Array.Empty(), sizeInbytes, vectorColumnLength };
-
+ yield return new object[] { 1, SqlVector.Null, Array.Empty(), vectorColumnLength };
+
// Following scenario is not supported in SqlClient.
// This can only be fixed with a behavior change that SqlParameter.Value is internally set to DBNull.Value if it is set to null.
- //yield return new object[] { 2, SqlVector.Null, Array.Empty(), sizeInbytes, vectorColumnLength };
-
- yield return new object[] { 3, SqlVector.Null, Array.Empty(), sizeInbytes, vectorColumnLength };
- yield return new object[] { 4, SqlVector.Null, Array.Empty(), sizeInbytes, vectorColumnLength };
+ //yield return new object[] { 2, SqlVector.Null, Array.Empty(), vectorColumnLength };
+
+ yield return new object[] { 3, SqlVector.Null, Array.Empty(), vectorColumnLength };
+ yield return new object[] { 4, SqlVector.Null, Array.Empty(), vectorColumnLength };
}
}
@@ -56,15 +57,15 @@ public sealed class NativeVectorFloat32Tests : IDisposable
{
private readonly ITestOutputHelper _output;
private static readonly string s_connectionString = ManualTesting.Tests.DataTestUtility.TCPConnectionString;
- private static readonly string s_tableName = DataTestUtility.GetUniqueName("VectorTestTable");
- private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetUniqueName("VectorBulkCopyTestTable");
+ private static readonly string s_tableName = DataTestUtility.GetShortName("VectorTestTable");
+ private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetShortName("VectorBulkCopyTestTable");
private static readonly string s_bulkCopySrcTableDef = $@"(Id INT PRIMARY KEY IDENTITY, VectorData vector(3) NULL)";
private static readonly string s_tableDefinition = $@"(Id INT PRIMARY KEY IDENTITY, VectorData vector(3) NULL)";
private static readonly string s_selectCmdString = $"SELECT VectorData FROM {s_tableName} ORDER BY Id DESC";
private static readonly string s_insertCmdString = $"INSERT INTO {s_tableName} (VectorData) VALUES (@VectorData)";
private static readonly string s_vectorParamName = $"@VectorData";
private static readonly string s_outputVectorParamName = $"@OutputVectorData";
- private static readonly string s_storedProcName = DataTestUtility.GetUniqueName("VectorsAsVarcharSp");
+ private static readonly string s_storedProcName = DataTestUtility.GetShortName("VectorsAsVarcharSp");
private static readonly string s_storedProcBody = $@"
{s_vectorParamName} vector(3), -- Input: Serialized float[] as JSON string
{s_outputVectorParamName} vector(3) OUTPUT -- Output: Echoed back from latest inserted row
@@ -101,10 +102,9 @@ public void Dispose()
DataTestUtility.DropStoredProcedure(connection, s_storedProcName);
}
- private void ValidateSqlVectorFloat32Object(bool isNull, SqlVector sqlVectorFloat32, float[] expectedData, int expectedSize, int expectedLength)
+ private void ValidateSqlVectorFloat32Object(bool isNull, SqlVector sqlVectorFloat32, float[] expectedData, int expectedLength)
{
Assert.Equal(expectedData, sqlVectorFloat32.Memory.ToArray());
- Assert.Equal(expectedSize, sqlVectorFloat32.Size);
Assert.Equal(expectedLength, sqlVectorFloat32.Length);
if (!isNull)
{
@@ -116,22 +116,22 @@ private void ValidateSqlVectorFloat32Object(bool isNull, SqlVector sqlVec
}
}
- private void ValidateInsertedData(SqlConnection connection, float[] expectedData, int expectedSize, int expectedLength)
+ private void ValidateInsertedData(SqlConnection connection, float[] expectedData, int expectedLength)
{
using var selectCmd = new SqlCommand(s_selectCmdString, connection);
using var reader = selectCmd.ExecuteReader();
Assert.True(reader.Read(), "No data found in the table.");
//For both null and non-null cases, validate the SqlVector object
- ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedSize, expectedLength);
- ValidateSqlVectorFloat32Object(reader.IsDBNull(0), reader.GetFieldValue>(0), expectedData, expectedSize, expectedLength);
- ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedSize, expectedLength);
+ ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength);
+ ValidateSqlVectorFloat32Object(reader.IsDBNull(0), reader.GetFieldValue>(0), expectedData, expectedLength);
+ ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength);
if (!reader.IsDBNull(0))
- {
- ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetValue(0), expectedData, expectedSize, expectedLength);
- ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader[0], expectedData, expectedSize, expectedLength);
- ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader["VectorData"], expectedData, expectedSize, expectedLength);
+ {
+ ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength);
+ ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader[0], expectedData, expectedLength);
+ ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader["VectorData"], expectedData, expectedLength);
Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetString(0)));
Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetSqlString(0).Value));
Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetFieldValue(0)));
@@ -147,13 +147,12 @@ private void ValidateInsertedData(SqlConnection connection, float[] expectedData
}
}
- [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))]
- [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData))]
+ [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
+ [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)]
public void TestSqlVectorFloat32ParameterInsertionAndReads(
int pattern,
object value,
float[] expectedValues,
- int expectedSize,
int expectedLength)
{
using var conn = new SqlConnection(s_connectionString);
@@ -171,7 +170,8 @@ public void TestSqlVectorFloat32ParameterInsertionAndReads(
},
2 => new SqlParameter(s_vectorParamName, value),
3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value },
- 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, new SqlVector(3).Size) { Value = value },
+ // Even if size is specified, the actual size is determined by the value passed and specified size is ignored.
+ 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value },
_ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}")
};
@@ -179,25 +179,25 @@ public void TestSqlVectorFloat32ParameterInsertionAndReads(
Assert.Equal(1, insertCmd.ExecuteNonQuery());
insertCmd.Parameters.Clear();
- ValidateInsertedData(conn, expectedValues, expectedSize, expectedLength);
+ ValidateInsertedData(conn, expectedValues, expectedLength);
}
- private async Task ValidateInsertedDataAsync(SqlConnection connection, float[] expectedData, int expectedSize, int expectedLength)
+ private async Task ValidateInsertedDataAsync(SqlConnection connection, float[] expectedData, int expectedLength)
{
using var selectCmd = new SqlCommand(s_selectCmdString, connection);
using var reader = await selectCmd.ExecuteReaderAsync();
Assert.True(await reader.ReadAsync(), "No data found in the table.");
//For both null and non-null cases, validate the SqlVector object
- ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedSize, expectedLength);
- ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), await reader.GetFieldValueAsync>(0), expectedData, expectedSize, expectedLength);
- ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedSize, expectedLength);
+ ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength);
+ ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), await reader.GetFieldValueAsync>(0), expectedData, expectedLength);
+ ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength);
if (!await reader.IsDBNullAsync(0))
{
- ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetValue(0), expectedData, expectedSize, expectedLength);
- ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader[0], expectedData, expectedSize, expectedLength);
- ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader["VectorData"], expectedData, expectedSize, expectedLength);
+ ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength);
+ ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader[0], expectedData, expectedLength);
+ ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader["VectorData"], expectedData, expectedLength);
Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetString(0)));
Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetSqlString(0).Value));
Assert.Equal(expectedData, JsonSerializer.Deserialize(await reader.GetFieldValueAsync(0)));
@@ -213,13 +213,12 @@ private async Task ValidateInsertedDataAsync(SqlConnection connection, float[] e
}
}
- [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))]
- [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData))]
+ [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
+ [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)]
public async Task TestSqlVectorFloat32ParameterInsertionAndReadsAsync(
int pattern,
object value,
float[] expectedValues,
- int expectedSize,
int expectedLength)
{
using var conn = new SqlConnection(s_connectionString);
@@ -237,7 +236,7 @@ public async Task TestSqlVectorFloat32ParameterInsertionAndReadsAsync(
},
2 => new SqlParameter(s_vectorParamName, value),
3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value },
- 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, new SqlVector(3).Size) { Value = value },
+ 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value },
_ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}")
};
@@ -245,16 +244,15 @@ public async Task TestSqlVectorFloat32ParameterInsertionAndReadsAsync(
Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync());
insertCmd.Parameters.Clear();
- await ValidateInsertedDataAsync(conn, expectedValues, expectedSize, expectedLength);
+ await ValidateInsertedDataAsync(conn, expectedValues, expectedLength);
}
- [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))]
- [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData))]
+ [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
+ [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)]
public void TestStoredProcParamsForVectorFloat32(
int pattern,
object value,
float[] expectedValues,
- int expectedSize,
int expectedLength)
{
//Create SP for test
@@ -277,7 +275,7 @@ public void TestStoredProcParamsForVectorFloat32(
},
2 => new SqlParameter(s_vectorParamName, value),
3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value },
- 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, new SqlVector(3).Size) { Value = value },
+ 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value },
_ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}")
};
command.Parameters.Add(inputParam);
@@ -287,7 +285,7 @@ public void TestStoredProcParamsForVectorFloat32(
ParameterName = s_outputVectorParamName,
SqlDbType = SqlDbTypeExtensions.Vector,
Direction = ParameterDirection.Output,
- Value = new SqlVector(3)
+ Value = SqlVector.CreateNull(VectorFloat32TestData.vectorColumnLength)
};
command.Parameters.Add(outputParam);
@@ -295,24 +293,23 @@ public void TestStoredProcParamsForVectorFloat32(
command.ExecuteNonQuery();
// Validate the output parameter
- var vector = outputParam.Value as SqlVector;
- ValidateSqlVectorFloat32Object(vector.IsNull, vector, expectedValues, expectedSize, expectedLength);
+ var vector = (SqlVector)outputParam.Value;
+ ValidateSqlVectorFloat32Object(vector.IsNull, vector, expectedValues, expectedLength);
// Validate error for conventional way of setting output parameters
command.Parameters.Clear();
command.Parameters.Add(inputParam);
- var outputParamWithoutVal = new SqlParameter(s_outputVectorParamName, SqlDbTypeExtensions.Vector, new SqlVector(3).Size) { Direction = ParameterDirection.Output };
+ var outputParamWithoutVal = new SqlParameter(s_outputVectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Direction = ParameterDirection.Output };
command.Parameters.Add(outputParamWithoutVal);
Assert.Throws(() => command.ExecuteNonQuery());
}
- [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))]
- [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData))]
+ [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
+ [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)]
public async Task TestStoredProcParamsForVectorFloat32Async(
int pattern,
object value,
float[] expectedValues,
- int expectedSize,
int expectedLength)
{
//Create SP for test
@@ -335,7 +332,7 @@ public async Task TestStoredProcParamsForVectorFloat32Async(
},
2 => new SqlParameter(s_vectorParamName, value),
3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value },
- 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, new SqlVector(3).Size) { Value = value },
+ 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value },
_ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}")
};
command.Parameters.Add(inputParam);
@@ -345,7 +342,7 @@ public async Task TestStoredProcParamsForVectorFloat32Async(
ParameterName = s_outputVectorParamName,
SqlDbType = SqlDbTypeExtensions.Vector,
Direction = ParameterDirection.Output,
- Value = new SqlVector(3)
+ Value = SqlVector.CreateNull(VectorFloat32TestData.vectorColumnLength)
};
command.Parameters.Add(outputParam);
@@ -353,18 +350,18 @@ public async Task TestStoredProcParamsForVectorFloat32Async(
await command.ExecuteNonQueryAsync();
// Validate the output parameter
- var vector = outputParam.Value as SqlVector;
- ValidateSqlVectorFloat32Object(vector.IsNull, vector, expectedValues, expectedSize, expectedLength);
+ var vector = (SqlVector)outputParam.Value;
+ ValidateSqlVectorFloat32Object(vector.IsNull, vector, expectedValues, expectedLength);
// Validate error for conventional way of setting output parameters
command.Parameters.Clear();
command.Parameters.Add(inputParam);
- var outputParamWithoutVal = new SqlParameter(s_outputVectorParamName, SqlDbTypeExtensions.Vector, new SqlVector(3).Size) { Direction = ParameterDirection.Output };
+ var outputParamWithoutVal = new SqlParameter(s_outputVectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Direction = ParameterDirection.Output };
command.Parameters.Add(outputParamWithoutVal);
await Assert.ThrowsAsync(async () => await command.ExecuteNonQueryAsync());
}
- [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))]
+ [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
[InlineData(1)]
[InlineData(2)]
public void TestBulkCopyFromSqlTable(int bulkCopySourceMode)
@@ -377,8 +374,8 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode)
DataTable table = null;
switch (bulkCopySourceMode)
{
-
- case 1:
+
+ case 1:
// Use SqlServer table as source
var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values (@VectorData)", sourceConnection);
var vectorParam = new SqlParameter(s_vectorParamName, new SqlVector(VectorFloat32TestData.testData));
@@ -403,8 +400,8 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode)
throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}");
}
-
-
+
+
//Bulkcopy from sql server table to destination table
using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, VectorData FROM {s_bulkCopySrcTableName}", sourceConnection);
using SqlDataReader reader = sourceDataCommand.ExecuteReader();
@@ -453,7 +450,6 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode)
Assert.True(!verifyReader.IsDBNull(0), "First row in the table is null.");
Assert.Equal(VectorFloat32TestData.testData, ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray());
Assert.Equal(VectorFloat32TestData.testData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length);
- Assert.Equal(VectorFloat32TestData.sizeInbytes, ((SqlVector)verifyReader.GetSqlVector(0)).Size);
// Verify that we have another row
Assert.True(verifyReader.Read(), "Second row not found in the table");
@@ -462,10 +458,9 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode)
Assert.True(verifyReader.IsDBNull(0));
Assert.Equal(Array.Empty(), ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray());
Assert.Equal(VectorFloat32TestData.testData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length);
- Assert.Equal(VectorFloat32TestData.sizeInbytes, ((SqlVector)verifyReader.GetSqlVector(0)).Size);
}
- [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))]
+ [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
[InlineData(1)]
[InlineData(2)]
public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode)
@@ -554,7 +549,6 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode)
var vector = await verifyReader.GetFieldValueAsync>(0);
Assert.Equal(VectorFloat32TestData.testData, vector.Memory.ToArray());
Assert.Equal(VectorFloat32TestData.testData.Length, vector.Length);
- Assert.Equal(VectorFloat32TestData.sizeInbytes, vector.Size);
// Verify that we have another row
Assert.True(await verifyReader.ReadAsync(), "Second row not found in the table");
@@ -564,16 +558,15 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode)
vector = await verifyReader.GetFieldValueAsync>(0);
Assert.Equal(Array.Empty(), vector.Memory.ToArray());
Assert.Equal(VectorFloat32TestData.testData.Length, vector.Length);
- Assert.Equal(VectorFloat32TestData.sizeInbytes, vector.Size);
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public void TestInsertVectorsFloat32WithPrepare()
{
SqlConnection conn = new SqlConnection(s_connectionString);
conn.Open();
SqlCommand command = new SqlCommand(s_insertCmdString, conn);
- SqlParameter vectorParam = new SqlParameter("@VectorData", SqlDbTypeExtensions.Vector, new SqlVector(3).Size);
+ SqlParameter vectorParam = new SqlParameter("@VectorData", SqlDbTypeExtensions.Vector);
command.Parameters.Add(vectorParam);
command.Prepare();
for (int i = 0; i < 10; i++)
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorAPIValidationTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorAPIValidationTest.cs
new file mode 100644
index 0000000000..27c857f5c5
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorAPIValidationTest.cs
@@ -0,0 +1,92 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.Data.SqlTypes;
+using Xunit;
+
+namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest
+{
+ public sealed class VectorAPIValidationTest
+ {
+ // We need these testcases to validate ref assembly for vector APIs
+ // Unit tests are covered under SqlVectorTest.cs
+ [Fact]
+ public void ValidateVectorSqlDbType()
+ {
+ // Validate that SqlVector is a valid type and has valid SqlDbType
+ Assert.True(typeof(SqlVector).IsValueType, "SqlVector should be a value type.");
+ Assert.Equal(36, (int)SqlDbTypeExtensions.Vector);
+ }
+
+ [Fact]
+ public void TestSqlVectorCreationAPIWithFloatArr()
+ {
+ // Validate ctor1 with float[] : public SqlVector(System.ReadOnlyMemory memory) { }
+ var testData = new float[] { 1.1f, 2.2f, 3.3f };
+ var vector = new SqlVector(testData);
+ Assert.Equal(testData, vector.Memory.ToArray());
+ Assert.Equal(3, vector.Length);
+ }
+
+ [Fact]
+ public void TestSqlVectorCreationAPIWithROM()
+ {
+ // Validate ctor2 with ReadOnlyMemory : public SqlVector(ReadOnlyMemory memory) { }
+ var testData = new ReadOnlyMemory(new float[] { 1.1f, 2.2f, 3.3f });
+ var vector = new SqlVector(testData);
+ Assert.Equal(testData.ToArray(), vector.Memory.ToArray());
+ Assert.Equal(3, vector.Length);
+ }
+
+ [Fact]
+ public void TestSqlVectorCreationAPICreateNull()
+ {
+ // Validate CreateNull method
+ var vector = SqlVector.CreateNull(5);
+ Assert.True(vector.IsNull);
+ Assert.Equal(5, vector.Length);
+ }
+
+ [Fact]
+ public void TestIsNullProperty()
+ {
+ //Validate IsNull property
+ var testData = new ReadOnlyMemory(new float[] { 1.1f, 2.2f, 3.3f });
+ var vector = new SqlVector(testData);
+ Assert.False(vector.IsNull, "IsNull should be false for non-null vector.");
+ vector = SqlVector.CreateNull(3);
+ Assert.True(vector.IsNull, "IsNull should be true for null vector.");
+ }
+
+ [Fact]
+ public void TestNullProperty()
+ {
+ // Validate Null property returns null
+ Assert.Null(SqlVector.Null);
+ }
+
+ [Fact]
+ public void TestLengthProperty()
+ {
+ // Validate Length property is correctly populated for null and non-null vectors
+ var testData = new float[] { 1.1f, 2.2f, 3.3f };
+ var vector = new SqlVector(testData);
+ Assert.Equal(3, vector.Length);
+ vector = SqlVector.CreateNull(3);
+ Assert.Equal(3, vector.Length);
+ }
+
+ [Fact]
+ public void TestMemoryProperty()
+ {
+ // Validate Memory property is correctly populated for non-null and null vectors
+ var testData = new float[] { 1.1f, 2.2f, 3.3f };
+ var vector = new SqlVector(testData);
+ Assert.Equal(testData, vector.Memory.ToArray());
+ vector = SqlVector.CreateNull(3);
+ Assert.True(vector.Memory.IsEmpty, "Null vector of given size point to empty ROM");
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorTypeBackwardCompatibilityTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorTypeBackwardCompatibilityTests.cs
index 5fb9cf7625..d38323b72f 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorTypeBackwardCompatibilityTests.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorTypeBackwardCompatibilityTests.cs
@@ -17,14 +17,14 @@ public sealed class VectorTypeBackwardCompatibilityTests : IDisposable
{
private readonly ITestOutputHelper _output;
private static readonly string s_connectionString = ManualTesting.Tests.DataTestUtility.TCPConnectionString;
- private static readonly string s_tableName = DataTestUtility.GetUniqueName("VectorTestTable");
- private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetUniqueName("VectorBulkCopyTestTable");
+ private static readonly string s_tableName = DataTestUtility.GetShortName("VectorTestTable");
+ private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetShortName("VectorBulkCopyTestTable");
private static readonly string s_bulkCopySrcTableDef = $@"(Id INT PRIMARY KEY IDENTITY, VectorData varchar(max) NULL)";
private static readonly string s_tableDefinition = $@"(Id INT PRIMARY KEY IDENTITY, VectorData vector(3) NULL)";
private static readonly string s_selectCmdString = $"SELECT VectorData FROM {s_tableName} ORDER BY Id DESC";
private static readonly string s_insertCmdString = $"INSERT INTO {s_tableName} (VectorData) VALUES (@VectorData)";
private static readonly string s_vectorParamName = $"@VectorData";
- private static readonly string s_storedProcName = DataTestUtility.GetUniqueName("VectorsAsVarcharSp");
+ private static readonly string s_storedProcName = DataTestUtility.GetShortName("VectorsAsVarcharSp");
private static readonly string s_storedProcBody = $@"
@InputVectorJson VARCHAR(MAX), -- Input: Serialized float[] as JSON string
@OutputVectorJson VARCHAR(MAX) OUTPUT -- Output: Echoed back from latest inserted row
@@ -81,7 +81,7 @@ private void ValidateInsertedData(SqlConnection connection, float[] expectedData
}
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public void TestVectorDataInsertionAsVarchar()
{
float[] data = { 1.1f, 2.2f, 3.3f };
@@ -173,7 +173,7 @@ private async Task ValidateInsertedDataAsync(SqlConnection connection, float[] e
}
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public async Task TestVectorParameterInitializationAsync()
{
float[] data = { 1.1f, 2.2f, 3.3f };
@@ -245,7 +245,7 @@ public async Task TestVectorParameterInitializationAsync()
await ValidateInsertedDataAsync(conn, null);
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public void TestVectorDataReadsAsVarchar()
{
float[] data = { 1.1f, 2.2f, 3.3f };
@@ -302,7 +302,7 @@ public void TestVectorDataReadsAsVarchar()
Assert.Throws(() => reader.GetFieldValue(0));
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public async Task TestVectorDataReadsAsVarcharAsync()
{
float[] data = { 1.1f, 2.2f, 3.3f };
@@ -359,7 +359,7 @@ public async Task TestVectorDataReadsAsVarcharAsync()
await Assert.ThrowsAsync(async () => await reader2.GetFieldValueAsync(0));
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public void TestStoredProcParamsForVectorAsVarchar()
{
// Test data
@@ -405,7 +405,7 @@ public void TestStoredProcParamsForVectorAsVarchar()
Assert.True(outputParam.Value == DBNull.Value);
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public async Task TestStoredProcParamsForVectorAsVarcharAsync()
{
// Test data
@@ -456,7 +456,7 @@ public async Task TestStoredProcParamsForVectorAsVarcharAsync()
Assert.True(outputParam.Value == DBNull.Value);
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public void TestSqlBulkCopyForVectorAsVarchar()
{
//Setup source with test data and create destination table for bulkcopy.
@@ -521,7 +521,7 @@ public void TestSqlBulkCopyForVectorAsVarchar()
Assert.True(verifyReader.IsDBNull(0));
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public async Task TestSqlBulkCopyForVectorAsVarcharAsync()
{
//Setup source with test data and create destination table for bulkcopy.
@@ -586,7 +586,7 @@ public async Task TestSqlBulkCopyForVectorAsVarcharAsync()
Assert.True(await verifyReader.IsDBNullAsync(0));
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer))]
public void TestInsertVectorsAsVarcharWithPrepare()
{
SqlConnection conn = new SqlConnection(s_connectionString);
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs
index b8649d43d2..4ae426fcbb 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs
@@ -483,7 +483,7 @@ public void ConnectionOpenAsyncErrorTest()
}).Dispose();
}
- private static void CollectStatisticsDiagnostics(Action sqlOperation, bool enableServerLogging = false, [CallerMemberName] string methodName = "")
+ private static void CollectStatisticsDiagnostics(Action sqlOperation, [CallerMemberName] string methodName = "")
{
bool statsLogged = false;
bool operationHasError = false;
@@ -670,10 +670,19 @@ private static void CollectStatisticsDiagnostics(Action sqlOperation, bo
{
Console.WriteLine(string.Format("Test: {0} Enabled Listeners", methodName));
- using (var server = TestTdsServer.StartServerWithQueryEngine(new DiagnosticsQueryEngine(), enableLog: enableServerLogging, methodName: methodName))
+
+ using (var server = new TdsServer(new DiagnosticsQueryEngine(), new TdsServerArguments()))
{
+ server.Start(methodName);
Console.WriteLine(string.Format("Test: {0} Started Server", methodName));
- sqlOperation(server.ConnectionString);
+
+ var connectionString = new SqlConnectionStringBuilder
+ {
+ DataSource = $"localhost,{server.EndPoint.Port}",
+ Encrypt = SqlConnectionEncryptOption.Optional
+ }.ConnectionString;
+
+ sqlOperation(connectionString);
Console.WriteLine(string.Format("Test: {0} SqlOperation Successful", methodName));
@@ -859,11 +868,17 @@ private static async Task CollectStatisticsDiagnosticsAsync(Func s
using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver))
{
Console.WriteLine(string.Format("Test: {0} Enabled Listeners", methodName));
- using (var server = TestTdsServer.StartServerWithQueryEngine(new DiagnosticsQueryEngine(), methodName: methodName))
+ using (var server = new TdsServer(new DiagnosticsQueryEngine(), new TdsServerArguments()))
{
+ server.Start(methodName);
Console.WriteLine(string.Format("Test: {0} Started Server", methodName));
- await sqlOperation(server.ConnectionString);
+ var connectionString = new SqlConnectionStringBuilder
+ {
+ DataSource = $"localhost,{server.EndPoint.Port}",
+ Encrypt = SqlConnectionEncryptOption.Optional
+ }.ConnectionString;
+ await sqlOperation(connectionString);
Console.WriteLine(string.Format("Test: {0} SqlOperation Successful", methodName));
@@ -890,7 +905,7 @@ private static T GetPropertyValueFromType(object obj, string propName)
public class DiagnosticsQueryEngine : QueryEngine
{
- public DiagnosticsQueryEngine() : base(new TDSServerArguments())
+ public DiagnosticsQueryEngine() : base(new TdsServerArguments())
{
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs
index 4e90bbf6c7..bd70084fab 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs
@@ -4,6 +4,7 @@
using System;
using System.Diagnostics;
using System.Reflection;
+using System.Threading.Tasks;
using System.Transactions;
using Xunit;
@@ -177,6 +178,143 @@ public void StasisCounters_Functional()
Assert.Equal(0, SqlClientEventSourceProps.StasisConnections);
}
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
+ public void TransactedConnectionPool_VerifyActiveConnectionCounters()
+ {
+ // This test verifies that the active connection count metric never goes negative
+ // when connections are returned to the pool while enlisted in a transaction.
+ // This is a regression test for issue #3640 where an extra DeactivateConnection
+ // call was causing the active connection count to go negative.
+
+ // Arrange
+ var stringBuilder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString)
+ {
+ Pooling = true,
+ Enlist = false,
+ MinPoolSize = 0,
+ MaxPoolSize = 10
+ };
+
+ // Clear pools to start fresh
+ ClearConnectionPools();
+
+ long initialActiveSoftConnections = SqlClientEventSourceProps.ActiveSoftConnections;
+ long initialActiveHardConnections = SqlClientEventSourceProps.ActiveHardConnections;
+ long initialActiveConnections = SqlClientEventSourceProps.ActiveConnections;
+
+ // Act and Assert
+ // Verify counters at each step in the lifecycle of a transacted connection
+ using (var txScope = new TransactionScope())
+ {
+ using (var conn = new SqlConnection(stringBuilder.ToString()))
+ {
+ conn.Open();
+ conn.EnlistTransaction(System.Transactions.Transaction.Current);
+
+ if (SupportsActiveConnectionCounters)
+ {
+ // Connection should be active
+ Assert.Equal(initialActiveSoftConnections + 1, SqlClientEventSourceProps.ActiveSoftConnections);
+ Assert.Equal(initialActiveHardConnections + 1, SqlClientEventSourceProps.ActiveHardConnections);
+ Assert.Equal(initialActiveConnections + 1, SqlClientEventSourceProps.ActiveConnections);
+ }
+
+ conn.Close();
+
+ // Connection is returned to pool but still in transaction (stasis)
+ if (SupportsActiveConnectionCounters)
+ {
+ // Connection should be deactivated (returned to pool)
+ Assert.Equal(initialActiveSoftConnections, SqlClientEventSourceProps.ActiveSoftConnections);
+ Assert.Equal(initialActiveHardConnections + 1, SqlClientEventSourceProps.ActiveHardConnections);
+ Assert.Equal(initialActiveConnections, SqlClientEventSourceProps.ActiveConnections);
+ }
+ }
+
+ // Completing the transaction after the connection is closed ensures that the connection
+ // is in the transacted pool at the time the transaction ends. This verifies that the
+ // transition from the transacted pool back to the main pool properly updates the counters.
+ txScope.Complete();
+ }
+
+ if (SupportsActiveConnectionCounters)
+ {
+ Assert.Equal(initialActiveSoftConnections, SqlClientEventSourceProps.ActiveSoftConnections);
+ Assert.Equal(initialActiveHardConnections + 1, SqlClientEventSourceProps.ActiveHardConnections);
+ Assert.Equal(initialActiveConnections, SqlClientEventSourceProps.ActiveConnections);
+ }
+ }
+
+ #if NET
+ // Note: DbConnection.CloseAsync is not available in .NET Framework
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
+ public async Task TransactedConnectionPool_VerifyActiveConnectionCounters_Async()
+ {
+ // This test verifies that the active connection count metric never goes negative
+ // when connections are returned to the pool while enlisted in a transaction.
+ // This is a regression test for issue #3640 where an extra DeactivateConnection
+ // call was causing the active connection count to go negative.
+
+ // Arrange
+ var stringBuilder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString)
+ {
+ Pooling = true,
+ Enlist = false,
+ MinPoolSize = 0,
+ MaxPoolSize = 10
+ };
+
+ // Clear pools to start fresh
+ ClearConnectionPools();
+
+ long initialActiveSoftConnections = SqlClientEventSourceProps.ActiveSoftConnections;
+ long initialActiveHardConnections = SqlClientEventSourceProps.ActiveHardConnections;
+ long initialActiveConnections = SqlClientEventSourceProps.ActiveConnections;
+
+ // Act and Assert
+ // Verify counters at each step in the lifecycle of a transacted connection
+ using (var txScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
+ {
+ using (var conn = new SqlConnection(stringBuilder.ToString()))
+ {
+ await conn.OpenAsync();
+ conn.EnlistTransaction(System.Transactions.Transaction.Current);
+
+ if (SupportsActiveConnectionCounters)
+ {
+ // Connection should be active
+ Assert.Equal(initialActiveSoftConnections + 1, SqlClientEventSourceProps.ActiveSoftConnections);
+ Assert.Equal(initialActiveHardConnections + 1, SqlClientEventSourceProps.ActiveHardConnections);
+ Assert.Equal(initialActiveConnections + 1, SqlClientEventSourceProps.ActiveConnections);
+ }
+
+ await conn.CloseAsync();
+
+ // Connection is returned to pool but still in transaction (stasis)
+ if (SupportsActiveConnectionCounters)
+ {
+ // Connection should be deactivated (returned to pool)
+ Assert.Equal(initialActiveSoftConnections, SqlClientEventSourceProps.ActiveSoftConnections);
+ Assert.Equal(initialActiveHardConnections + 1, SqlClientEventSourceProps.ActiveHardConnections);
+ Assert.Equal(initialActiveConnections, SqlClientEventSourceProps.ActiveConnections);
+ }
+ }
+
+ // Completing the transaction after the connection is closed ensures that the connection
+ // is in the transacted pool at the time the transaction ends. This verifies that the
+ // transition from the transacted pool back to the main pool properly updates the counters.
+ txScope.Complete();
+ }
+
+ if (SupportsActiveConnectionCounters)
+ {
+ Assert.Equal(initialActiveSoftConnections, SqlClientEventSourceProps.ActiveSoftConnections);
+ Assert.Equal(initialActiveHardConnections + 1, SqlClientEventSourceProps.ActiveHardConnections);
+ Assert.Equal(initialActiveConnections, SqlClientEventSourceProps.ActiveConnections);
+ }
+ }
+ #endif
+
[ActiveIssue("https://github.com/dotnet/SqlClient/issues/3031")]
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
public void ReclaimedConnectionsCounter_Functional()
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/TestTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/TestTdsServer.cs
deleted file mode 100644
index 45a817c46e..0000000000
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/TestTdsServer.cs
+++ /dev/null
@@ -1,93 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Linq;
-using System.Net;
-using System.Net.Sockets;
-using System.Runtime.CompilerServices;
-using System.Security.Authentication;
-using System.Security.Cryptography.X509Certificates;
-using Microsoft.SqlServer.TDS.EndPoint;
-using Microsoft.SqlServer.TDS.PreLogin;
-using Microsoft.SqlServer.TDS.Servers;
-
-namespace Microsoft.Data.SqlClient.ManualTesting.Tests
-{
- internal class TestTdsServer : GenericTDSServer, IDisposable
- {
- private const int DefaultConnectionTimeout = 5;
-
- private TDSServerEndPoint _endpoint = null;
-
- private SqlConnectionStringBuilder _connectionStringBuilder;
-
- public TestTdsServer(TDSServerArguments args) : base(args) { }
-
- public TestTdsServer(QueryEngine engine, TDSServerArguments args) : base(args)
- {
- Engine = engine;
- }
-
- public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool enableFedAuth = false, bool enableLog = false,
- int connectionTimeout = DefaultConnectionTimeout, [CallerMemberName] string methodName = "",
- X509Certificate2 encryptionCertificate = null, SslProtocols encryptionProtocols = SslProtocols.Tls12, TDSPreLoginTokenEncryptionType encryptionType = TDSPreLoginTokenEncryptionType.NotSupported)
- {
- TDSServerArguments args = new TDSServerArguments()
- {
- Log = enableLog ? Console.Out : null,
- };
-
- if (enableFedAuth)
- {
- args.FedAuthRequiredPreLoginOption = SqlServer.TDS.PreLogin.TdsPreLoginFedAuthRequiredOption.FedAuthRequired;
- }
-
- args.EncryptionCertificate = encryptionCertificate;
- args.EncryptionProtocols = encryptionProtocols;
- args.Encryption = encryptionType;
-
- TestTdsServer server = engine == null ? new TestTdsServer(args) : new TestTdsServer(engine, args);
-
- server._endpoint = new TDSServerEndPoint(server) { ServerEndPoint = new IPEndPoint(IPAddress.Any, 0) };
- server._endpoint.EndpointName = methodName;
- // The server EventLog should be enabled as it logs the exceptions.
- server._endpoint.EventLog = enableLog ? Console.Out : null;
- server._endpoint.Start();
-
- int port = server._endpoint.ServerEndPoint.Port;
-
- server._connectionStringBuilder = new SqlConnectionStringBuilder()
- {
- DataSource = "localhost," + port,
- ConnectTimeout = connectionTimeout,
- };
-
- if (encryptionType == TDSPreLoginTokenEncryptionType.Off ||
- encryptionType == TDSPreLoginTokenEncryptionType.None ||
- encryptionType == TDSPreLoginTokenEncryptionType.NotSupported)
- {
- server._connectionStringBuilder.Encrypt = SqlConnectionEncryptOption.Optional;
- }
- else
- {
- server._connectionStringBuilder.Encrypt = SqlConnectionEncryptOption.Mandatory;
- }
-
- server.ConnectionString = server._connectionStringBuilder.ConnectionString;
- return server;
- }
-
- public static TestTdsServer StartTestServer(bool enableFedAuth = false, bool enableLog = false,
- int connectionTimeout = DefaultConnectionTimeout, [CallerMemberName] string methodName = "",
- X509Certificate2 encryptionCertificate = null, SslProtocols encryptionProtocols = SslProtocols.Tls12, TDSPreLoginTokenEncryptionType encryptionType = TDSPreLoginTokenEncryptionType.NotSupported)
- {
- return StartServerWithQueryEngine(null, enableFedAuth, enableLog, connectionTimeout, methodName, encryptionCertificate, encryptionProtocols, encryptionType);
- }
-
- public void Dispose() => _endpoint?.Stop();
-
- public string ConnectionString { get; private set; }
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs
index 40fde20faa..bcb23d11bd 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs
@@ -7,12 +7,22 @@
using System.Xml;
using System.Xml.XPath;
using Xunit;
+using Xunit.Abstractions;
+
+#nullable enable
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
{
public class XEventsTracingTest
{
- [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ private readonly string _testName;
+
+ public XEventsTracingTest(ITestOutputHelper outputHelper)
+ {
+ _testName = DataTestUtility.CurrentTestName(outputHelper);
+ }
+
+ [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))]
[InlineData("SELECT @@VERSION", System.Data.CommandType.Text, "sql_statement_starting")]
[InlineData("sp_help", System.Data.CommandType.StoredProcedure, "rpc_starting")]
public void XEventActivityIDConsistentWithTracing(string query, System.Data.CommandType commandType, string xEvent)
@@ -21,42 +31,46 @@ public void XEventActivityIDConsistentWithTracing(string query, System.Data.Comm
// where it can be recorded in an XEvent session. This is documented at:
// https://learn.microsoft.com/en-us/sql/relational-databases/native-client/features/accessing-diagnostic-information-in-the-extended-events-log
- using (SqlConnection xEventManagementConnection = new SqlConnection(DataTestUtility.TCPConnectionString))
- using (DataTestUtility.XEventScope xEventSession = new DataTestUtility.XEventScope(xEventManagementConnection,
- @"ADD EVENT SQL_STATEMENT_STARTING (ACTION (client_connection_id)),
- ADD EVENT RPC_STARTING (ACTION (client_connection_id))",
- "ADD TARGET ring_buffer"))
- {
- Guid connectionId;
- HashSet ids;
+ using SqlConnection activityConnection = new(DataTestUtility.TCPConnectionString);
+ activityConnection.Open();
- using (DataTestUtility.MDSEventListener TraceListener = new())
- using (SqlConnection connection = new(DataTestUtility.TCPConnectionString))
- {
- connection.Open();
- connectionId = connection.ClientConnectionId;
+ Guid connectionId = activityConnection.ClientConnectionId;
+ HashSet ids;
- using SqlCommand command = new(query, connection) { CommandType = commandType };
- using SqlDataReader reader = command.ExecuteReader();
- while (reader.Read())
- {
- // Flush data
- }
+ using SqlConnection xEventManagementConnection = new(DataTestUtility.TCPConnectionString);
+ xEventManagementConnection.Open();
+
+ using DataTestUtility.XEventScope xEventSession = new(
+ _testName,
+ xEventManagementConnection,
+ $@"ADD EVENT SQL_STATEMENT_STARTING (ACTION (client_connection_id) WHERE (client_connection_id='{connectionId}')),
+ ADD EVENT RPC_STARTING (ACTION (client_connection_id) WHERE (client_connection_id='{connectionId}'))",
+ "ADD TARGET ring_buffer");
- ids = TraceListener.ActivityIDs;
+ using (DataTestUtility.MDSEventListener TraceListener = new())
+ {
+ using SqlCommand command = new(query, activityConnection) { CommandType = commandType };
+ using SqlDataReader reader = command.ExecuteReader();
+ while (reader.Read())
+ {
+ // Flush data
}
- XmlDocument eventList = xEventSession.GetEvents();
- // Get the associated activity ID from the XEvent session. We expect to see the same ID in the trace as well.
- string activityId = GetCommandActivityId(query, xEvent, connectionId, eventList);
-
- Assert.Contains(activityId, ids);
+ ids = TraceListener.ActivityIDs;
}
+
+ XmlDocument eventList = xEventSession.GetEvents();
+ // Get the associated activity ID from the XEvent session. We expect to see the same ID in the trace as well.
+ string activityId = GetCommandActivityId(query, xEvent, connectionId, eventList);
+
+ Assert.Contains(activityId, ids);
}
private static string GetCommandActivityId(string commandText, string eventName, Guid connectionId, XmlDocument xEvents)
{
- XPathNavigator xPathRoot = xEvents.CreateNavigator();
+ XPathNavigator? xPathRoot = xEvents.CreateNavigator();
+ Assert.NotNull(xPathRoot);
+
// The transferred activity ID is attached to the "attach_activity_id_xfer" action within
// the "sql_statement_starting" and the "rpc_starting" events.
XPathNodeIterator statementStartingQuery = xPathRoot.Select(
@@ -67,7 +81,9 @@ private static string GetCommandActivityId(string commandText, string eventName,
Assert.Equal(1, statementStartingQuery.Count);
Assert.True(statementStartingQuery.MoveNext());
- XPathNavigator activityIdElement = statementStartingQuery.Current.SelectSingleNode("action[@name='attach_activity_id_xfer']/value");
+ XPathNavigator? current = statementStartingQuery.Current;
+ Assert.NotNull(current);
+ XPathNavigator? activityIdElement = current.SelectSingleNode("action[@name='attach_activity_id_xfer']/value");
Assert.NotNull(activityIdElement);
Assert.NotNull(activityIdElement.Value);
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props
new file mode 100644
index 0000000000..66fbacae6c
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+ net462;net47;net471;net472;net48;net481;net8.0;net9.0
+
+
+ latest
+
+
+
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props
new file mode 100644
index 0000000000..45b1a5018f
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+ $(MdsPackageVersion)
+
+
+
+
+
+
+
+
+
+ 6.1.0-preview2.25178.5
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/IMonitorLoader.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/IMonitorLoader.cs
new file mode 100644
index 0000000000..1dcbde121f
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/IMonitorLoader.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace Monitoring
+{
+ public interface IMonitorLoader
+ {
+ string HostMachine { get; set; }
+ string AssemblyPath { get; set; }
+ string TestName { get; set; }
+ bool Enabled { get; set; }
+
+ void Action(MonitorLoaderUtils.MonitorAction monitoraction);
+ void AddPerfData(MonitorMetrics data);
+ Dictionary GetPerfData();
+ }
+
+ public class MonitorLoaderUtils
+ {
+ public enum MonitorAction
+ {
+ Initialize,
+ Start,
+ Stop,
+ DoNothing
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/IMonitorLoader.csproj b/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/IMonitorLoader.csproj
new file mode 100644
index 0000000000..0968e1837f
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/IMonitorLoader.csproj
@@ -0,0 +1,6 @@
+
+
+ Monitoring
+ Monitoring
+
+
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/MonitorMetrics.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/MonitorMetrics.cs
new file mode 100644
index 0000000000..ed37544e4e
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/MonitorMetrics.cs
@@ -0,0 +1,96 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace Monitoring
+{
+ public class MonitorMetrics
+ {
+ private string _name;
+ private string _strValue;
+ private string _unit;
+ private bool _isPrimary;
+ private bool _isHigherBetter;
+ private double _dblValue;
+ private long _lngValue;
+ private char _valueType; // D=double, L=long, S=String
+
+ public MonitorMetrics(string name, string value, string unit, bool HigherIsBetter, bool Primary)
+ {
+ _name = name;
+ _strValue = value;
+ _unit = unit;
+ _valueType = 'S';
+ _isHigherBetter = HigherIsBetter;
+ _isPrimary = Primary;
+ }
+
+ public MonitorMetrics(string name, double value, string unit, bool HigherIsBetter, bool Primary)
+ {
+ _name = name;
+ _dblValue = value;
+ _unit = unit;
+ _valueType = 'D';
+ _isHigherBetter = HigherIsBetter;
+ _isPrimary = Primary;
+ }
+
+ public MonitorMetrics(string name, long value, string unit, bool HigherIsBetter, bool Primary)
+ {
+ _name = name;
+ _lngValue = value;
+ _unit = unit;
+ _valueType = 'L';
+ _isHigherBetter = HigherIsBetter;
+ _isPrimary = Primary;
+ }
+
+ public string GetName()
+ {
+ return _name;
+ }
+
+ public string GetUnit()
+ {
+ return _unit;
+ }
+
+ public bool GetPrimary()
+ {
+ return _isPrimary;
+ }
+
+ public bool GetHigherIsBetter()
+ {
+ return _isHigherBetter;
+ }
+
+ public char GetValueType()
+ {
+ return _valueType;
+ }
+
+ public string GetStringValue()
+ {
+ if (_valueType == 'S')
+ return _strValue;
+ throw new Exception("Value is not a string");
+ }
+
+ public double GetDoubleValue()
+ {
+ if (_valueType == 'D')
+ return _dblValue;
+ throw new Exception("Value is not a double");
+ }
+
+ public long GetLongValue()
+ {
+ if (_valueType == 'L')
+ return _lngValue;
+ throw new Exception("Value is not a long");
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/NuGet.config b/src/Microsoft.Data.SqlClient/tests/StressTests/NuGet.config
new file mode 100644
index 0000000000..19c2531f5d
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/NuGet.config
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/Readme.md b/src/Microsoft.Data.SqlClient/tests/StressTests/Readme.md
new file mode 100644
index 0000000000..3ae5c9e3df
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/Readme.md
@@ -0,0 +1,230 @@
+# Microsoft.Data.SqlClient Stress Test
+
+This Stress testing application for `Microsoft.Data.SqlClient` is under progress.
+
+This project intends to help finding a certain level of effectiveness under
+unfavorable conditions, and verifying the mode of failures.
+
+This is a console application targeting all frameworks supported by MDS,
+currently:
+
+- .NET 8.0
+- .NET T9.0
+- .NET Framework 4.6.2
+- .NET Framework 4.7
+- .NET Framework 4.7.1
+- .NET Framework 4.7.2
+- .NET Framework 4.8
+- .NET Framework 4.8.1
+
+## Purpose of application for developers
+
+Define fuzz tests for all new features/APIs in the driver and to be run before
+every GA release.
+
+## Pre-Requisites
+
+Required in the config file:
+
+|Field|Values|Description|
+|-|-|-|
+|`name`||Stress testing source configuration name.|
+|`type`|`SqlServer`|Only `SqlServer` is acceptable.|
+|`isDefault`|`true`, `false`|If there is a source node with `isDefault=true`, this node is returned.|
+|`dataSource`||SQL Server data source name.|
+|`user`||User Id to connect the server.|
+|`password`||Paired password with the user.|
+|`supportsWindowsAuthentication`|`true`, `false`|Tries to use integrated security in connection string mixed with SQL Server authentication if it set to `true` by applying the randomization.|
+|`isLocal`|`true`, `false`|`true` means database is local.|
+|`disableMultiSubnetFailover`|`true`, `false`|Tries to add Multi-subnet Failover fake host entries when it equals `true`.|
+|`disableNamedPipes`|`true`, `false`|`true` means the connections will create just using tcp protocol.|
+|`encrypt`|`true`, `false`|Assigns the encrypt property of the connection strings.|
+
+Note: The database user must have permission to create and drop databases.
+Each execution of the stress tests will create a database with a name like:
+
+- `StressTests-`
+
+The database will be dropped as a best effort once testing is complete. This
+allows for multiple test runs to execute in parallel against the same database
+server without colliding.
+
+## Adding new Tests
+
+- [ToDo]
+
+## Building the application
+
+To build the application using the `StressTests.slnx` solution:
+
+```bash
+dotnet build [-c|--configuration ]
+```
+
+```bash
+# Builds the application for the Client Os in `Debug` Configuration for `AnyCpu`
+# platform.
+#
+# All supported target frameworks are built by default.
+
+$ dotnet build
+```
+
+```bash
+# Build the application for .Net framework 4.8.1 with `Debug` configuration.
+
+$ dotnet build -f net481
+```
+
+```bash
+# Build the application for .Net 9.0 with `Release` configuration.
+
+$ dotnet build -f net9.0 -c Release
+```
+
+```bash
+# Cleans all build directories
+
+$ dotnet clean
+```
+
+## Running tests
+
+After building the application, find the built folder with target framework and
+run the `stresstest.exe` file with required arguments.
+
+Find the result in a log file inside the `logs` folder besides the command
+prompt.
+
+You may specify the config file by supplying an environment variable that
+points to the file:
+
+- `STRESS_CONFIG_FILE=/path/to/my/config.jsonc`
+
+## Command prompt
+
+You must run the stress tests from the root of the Stress Tests project
+directory (i.e. the same directory this readme file is in).
+
+```bash
+# Linux
+$ cd /home/paul/dev/SqlClient/src/Microsoft.Data.SqlClient/tests/StressTests
+
+# Via dotnet run CLI:
+$ dotnet run --no-build -f net9.0 --project SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj -- -a SqlClient.Stress.Tests
+
+# Via dotnet CLI:
+$ dotnet SqlClient.Stress.Runner/bin/Debug/net9.0/stresstest.dll -a SqlClient.Stress.Tests
+
+# With a specific config file and all output to console:
+$ dotnet run --no-build -f net9.0 --project SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj -e STRESS_CONFIG_FILE=/path/to/config.jsonc -- -a SqlClient.Stress.Tests -console
+```
+
+```powershell
+# Windows
+> cd \dev\SqlClient\src\Microsoft.Data.SqlClient\tests\StressTests
+
+# Via dotnet run CLI:
+> dotnet run --no-build -f net9.0 --project SqlClient.Stress.Runner\SqlClient.Stress.Runner.csproj -- -a SqlClient.Stress.Tests
+
+# Via executable:
+> .\SqlClient.Stress.Runner\bin\Debug\net481\stresstest.exe -a SqlClient.Stress.Tests
+
+# With a specific config file and all output to console:
+> dotnet run --no-build -f net9.0 --project SqlClient.Stress.Runner\SqlClient.Stress.Runner.csproj -e STRESS_CONFIG_FILE=c:\path\to\config.jsonc -- -a SqlClient.Stress.Tests -console
+```
+
+## Supported arguments
+
+|Argument|Values|Description|
+|-|-|-|
+|-all||Run all tests - best for debugging, not perf measurements.|
+|-verify||Run in functional verification mode. [not implemented]|
+|-duration|<n>|Duration of the test in seconds. Default value is 1 second.|
+|-threads|<n>|Number of threads to use. Default value is 16.|
+|-override|<name> <value>|Override the value of a test property.|
+|-test|<name1;name2>|Run specific test(s).|
+|-debug||Print process ID in the beginning and wait for Enter (to give your time to attach the debugger).|
+|-console||Emit all output to the console instead of a log file.|
+|-exceptionThreshold|<n>|An optional limit on exceptions which will be caught. When reached, test will halt.|
+|-monitorenabled|true, false|True or False to enable monitoring. Default is false [not implemented]|
+|-randomSeed||Enables setting of the random number generator used internally. This serves both the purpose of helping to improve reproducibility and making it deterministic from Chess's perspective for a given schedule. Default is 0.|
+|-filter|<filter>|Run tests whose stress test attributes match the given filter. Filter is not applied if attribute does not implement ITestAttributeFilter. Example: -filter TestType=Query,Update;IsServerTest=True|
+|-printMethodName||Print tests' title in console window|
+|-deadlockdetection|true, false|True or False to enable deadlock detection. Default is `false`.|
+
+```powershell
+# Run the application for a built target framework and all discovered tests
+# without debugger attached.
+
+> .\stresstest.exe -a SqlClient.Stress.Tests -all
+```
+
+```powershell
+# Run the application for a built target framework and all discovered tests
+# without debugger attached and shows the test methods' names.
+
+> .\stresstest.exe -a SqlClient.Stress.Tests -all -printMethodName
+```
+
+```powershell
+# Run the application for a built target framework and all discovered tests and
+# will wait for debugger to be attached.
+
+> .\stresstest.exe -a SqlClient.Stress.Tests -all -debug
+```
+
+```powershell
+# Run the application for a built target framework and
+# "TestExecuteXmlReaderAsyncCancellation" test without debugger attached.
+
+> .\stresstest.exe -a SqlClient.Stress.Tests -test TestExecuteXmlReaderAsyncCancellation
+```
+
+```powershell
+# Run the application for a built target framework and
+# "TestExecuteXmlReaderAsyncCancellation" test without debugger attached.
+
+> .\stresstest.exe -a SqlClient.Stress.Tests -test TestExecuteXmlReaderAsyncCancellation
+```
+
+```powershell
+# Run the application for a built target framework and all discovered tests
+# without debugger attached for 10 seconds.
+
+> .\stresstest.exe -a SqlClient.Stress.Tests -all -duration 10
+```
+
+```powershell
+# Run the application for a built target framework and all discovered tests
+# without debugger attached with 5 threads.
+
+> .\stresstest.exe -a SqlClient.Stress.Tests -all -threads 5
+```
+
+```powershell
+# Run the application for a built target framework and all discovered tests
+# without debugger attached and dead lock detection process.
+
+> .\stresstest.exe -a SqlClient.Stress.Tests -all -deadlockdetection true
+```
+
+```powershell
+# Run the application for a built target framework and all discovered tests
+# without debugger attached with overriding the weight property with value 15.
+
+> .\stresstest.exe -a SqlClient.Stress.Tests -all -override Weight 15
+```
+
+```powershell
+# Run the application for a built target framework and all discovered tests
+# without debugger attached with injecting random seed of 5.
+
+> .\stresstest.exe -a SqlClient.Stress.Tests -all -randomSeed 5
+```
+
+## Further thoughts
+
+- Implement the uncompleted arguments.
+- Add more tests.
+- Add support running tests with **System.Data.SqlClient** too.
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalExceptionHandlerAttribute.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalExceptionHandlerAttribute.cs
new file mode 100644
index 0000000000..810580d9f8
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalExceptionHandlerAttribute.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace DPStressHarness
+{
+ [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
+ public class GlobalExceptionHandlerAttribute : Attribute
+ {
+ public GlobalExceptionHandlerAttribute()
+ {
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalTestCleanupAttribute.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalTestCleanupAttribute.cs
new file mode 100644
index 0000000000..2159d2630e
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalTestCleanupAttribute.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace DPStressHarness
+{
+ [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
+ public class GlobalTestCleanupAttribute : Attribute
+ {
+ public GlobalTestCleanupAttribute()
+ {
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalTestSetupAttribute.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalTestSetupAttribute.cs
new file mode 100644
index 0000000000..00ed3d5b05
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalTestSetupAttribute.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace DPStressHarness
+{
+ [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
+ public class GlobalTestSetupAttribute : Attribute
+ {
+ public GlobalTestSetupAttribute()
+ {
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestAttribute.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestAttribute.cs
new file mode 100644
index 0000000000..3146c2d808
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestAttribute.cs
@@ -0,0 +1,272 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace DPStressHarness
+{
+ public enum TestPriority
+ {
+ BVT = 0,
+ High = 1,
+ Medium = 2,
+ Low = 3
+ }
+
+ public class TestAttributeBase : Attribute
+ {
+ private string _title;
+ private string _description = "none provided";
+ private string _applicationName = "unknown";
+ private string _improvement = "ADONETV3";
+ private string _owner = "unknown";
+ private string _category = "unknown";
+ private TestPriority _priority = TestPriority.BVT;
+
+ public TestAttributeBase(string title)
+ {
+ _title = title;
+ }
+
+ public string Title
+ {
+ get { return _title; }
+ set { _title = value; }
+ }
+
+ public string Description
+ {
+ get { return _description; }
+ set { _description = value; }
+ }
+
+ public string Improvement
+ {
+ get { return _improvement; }
+ set { _improvement = value; }
+ }
+
+ public string Owner
+ {
+ get { return _owner; }
+ set { _owner = value; }
+ }
+
+ public string ApplicationName
+ {
+ get { return _applicationName; }
+ set { _applicationName = value; }
+ }
+
+ public TestPriority Priority
+ {
+ get { return _priority; }
+ set { _priority = value; }
+ }
+
+ public string Category
+ {
+ get { return _category; }
+ set { _category = value; }
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+ public class TestAttribute : TestAttributeBase
+ {
+ private int _warmupIterations = 0;
+ private int _testIterations = 1;
+
+ public TestAttribute(string title) : base(title)
+ {
+ }
+
+ public int WarmupIterations
+ {
+ get
+ {
+ string propName = "WarmupIterations";
+
+ if (TestMetrics.Overrides.ContainsKey(propName))
+ {
+ return int.Parse(TestMetrics.Overrides[propName]);
+ }
+ else
+ {
+ return _warmupIterations;
+ }
+ }
+ set { _warmupIterations = value; }
+ }
+
+ public int TestIterations
+ {
+ get
+ {
+ string propName = "TestIterations";
+
+ if (TestMetrics.Overrides.ContainsKey(propName))
+ {
+ return int.Parse(TestMetrics.Overrides[propName]);
+ }
+ else
+ {
+ return _testIterations;
+ }
+ }
+ set { _testIterations = value; }
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+ public class StressTestAttribute : TestAttributeBase
+ {
+ private int _weight = 1;
+
+ public StressTestAttribute(string title)
+ : base(title)
+ {
+ }
+
+ public int Weight
+ {
+ get { return _weight; }
+ set { _weight = value; }
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+ public class MultiThreadedTestAttribute : TestAttributeBase
+ {
+ private int _warmupDuration = 60;
+ private int _testDuration = 60;
+ private int _threads = 16;
+
+ public MultiThreadedTestAttribute(string title)
+ : base(title)
+ {
+ }
+
+ public int WarmupDuration
+ {
+ get
+ {
+ string propName = "WarmupDuration";
+
+ if (TestMetrics.Overrides.ContainsKey(propName))
+ {
+ return int.Parse(TestMetrics.Overrides[propName]);
+ }
+ else
+ {
+ return _warmupDuration;
+ }
+ }
+ set { _warmupDuration = value; }
+ }
+
+ public int TestDuration
+ {
+ get
+ {
+ string propName = "TestDuration";
+
+ if (TestMetrics.Overrides.ContainsKey(propName))
+ {
+ return int.Parse(TestMetrics.Overrides[propName]);
+ }
+ else
+ {
+ return _testDuration;
+ }
+ }
+ set { _testDuration = value; }
+ }
+
+ public int Threads
+ {
+ get
+ {
+ string propName = "Threads";
+
+ if (TestMetrics.Overrides.ContainsKey(propName))
+ {
+ return int.Parse(TestMetrics.Overrides[propName]);
+ }
+ else
+ {
+ return _threads;
+ }
+ }
+ set { _threads = value; }
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+ public class ThreadPoolTestAttribute : TestAttributeBase
+ {
+ private int _warmupDuration = 60;
+ private int _testDuration = 60;
+ private int _threads = 64;
+
+ public ThreadPoolTestAttribute(string title)
+ : base(title)
+ {
+ }
+
+ public int WarmupDuration
+ {
+ get
+ {
+ string propName = "WarmupDuration";
+
+ if (TestMetrics.Overrides.ContainsKey(propName))
+ {
+ return int.Parse(TestMetrics.Overrides[propName]);
+ }
+ else
+ {
+ return _warmupDuration;
+ }
+ }
+ set { _warmupDuration = value; }
+ }
+
+ public int TestDuration
+ {
+ get
+ {
+ string propName = "TestDuration";
+
+ if (TestMetrics.Overrides.ContainsKey(propName))
+ {
+ return int.Parse(TestMetrics.Overrides[propName]);
+ }
+ else
+ {
+ return _testDuration;
+ }
+ }
+ set { _testDuration = value; }
+ }
+
+ public int Threads
+ {
+ get
+ {
+ string propName = "Threads";
+
+ if (TestMetrics.Overrides.ContainsKey(propName))
+ {
+ return int.Parse(TestMetrics.Overrides[propName]);
+ }
+ else
+ {
+ return _threads;
+ }
+ }
+ set { _threads = value; }
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestCleanupAttribute.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestCleanupAttribute.cs
new file mode 100644
index 0000000000..32bc5ee6bc
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestCleanupAttribute.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace DPStressHarness
+{
+ [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
+ public class TestCleanupAttribute : Attribute
+ {
+ public TestCleanupAttribute()
+ {
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestSetupAttribute.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestSetupAttribute.cs
new file mode 100644
index 0000000000..5626032b69
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestSetupAttribute.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace DPStressHarness
+{
+ [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
+ public class TestSetupAttribute : Attribute
+ {
+ public TestSetupAttribute()
+ {
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestVariationAttribute.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestVariationAttribute.cs
new file mode 100644
index 0000000000..e54acfa969
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestVariationAttribute.cs
@@ -0,0 +1,35 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DPStressHarness
+{
+ [AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = true)]
+ public class TestVariationAttribute : Attribute
+ {
+ private string _variationName;
+ private object _variationValue;
+
+ public TestVariationAttribute(string variationName, object variationValue)
+ {
+ _variationName = variationName;
+ _variationValue = variationValue;
+ }
+
+ public string VariationName
+ {
+ get { return _variationName; }
+ set { _variationName = value; }
+ }
+
+ public object VariationValue
+ {
+ get { return _variationValue; }
+ set { _variationValue = value; }
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/DeadlockDetection.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/DeadlockDetection.cs
new file mode 100644
index 0000000000..50fc6d3d7a
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/DeadlockDetection.cs
@@ -0,0 +1,194 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace DPStressHarness
+{
+ public class DeadlockDetection
+ {
+ ///
+ /// Information for a thread relating to deadlock detection. All of its information is stored in a reference object to make updating it easier.
+ ///
+ private class ThreadInfo
+ {
+ public ThreadInfo(long dueTime)
+ {
+ this.DueTime = dueTime;
+ }
+
+ ///
+ /// The time (in ticks) when the thread should be completed
+ ///
+ public long DueTime;
+
+ ///
+ /// True if the thread should not be aborted
+ ///
+ public bool DisableAbort;
+
+ ///
+ /// The time when DisableAbort was set to true
+ ///
+ public long DisableAbortTime;
+ }
+
+ ///
+ /// Maximum time that a test thread (i.e. a thread that is directly executing a [StressTest] method) can
+ /// execute before it is considered to be deadlocked. This should be longer than the
+ /// TaskThreadDeadlockTimeoutTicks because if the test is waiting for a task then the test will always
+ /// take longer to execute than the task.
+ ///
+ public const long TestThreadDeadlockTimeoutTicks = 20 * 60 * TimeSpan.TicksPerSecond;
+
+ ///
+ /// Maximum time that any Task can execute before it is considered to be deadlocked
+ ///
+ public const long TaskThreadDeadlockTimeoutTicks = 10 * 60 * TimeSpan.TicksPerSecond;
+
+ ///
+ /// Dictionary that maps Threads to the time (in ticks) when they should be completed. If they are not completed by that time then
+ /// they are considered to be deadlocked.
+ ///
+ private static ConcurrentDictionary s_threadDueTimes = null;
+
+ ///
+ /// Timer that scans through _threadDueTimes to find deadlocked threads
+ ///
+ private static Timer s_deadlockWatchdog = null;
+
+ ///
+ /// Interval of _deadlockWatchdog, in milliseconds
+ ///
+ private const int _watchdogIntervalMs = 60 * 1000;
+
+ ///
+ /// true if deadlock detection is enabled, otherwise false. Should be set only at process startup.
+ ///
+ private static bool s_isEnabled = false;
+
+ public static bool IsEnabled => s_isEnabled;
+
+ ///
+ /// Enables deadlock detection.
+ ///
+ public static void Enable()
+ {
+ // Switch out the default TaskScheduler. We must use reflection because it is private.
+ FieldInfo defaultTaskScheduler = typeof(TaskScheduler).GetField("s_defaultTaskScheduler", BindingFlags.NonPublic | BindingFlags.Static);
+ DeadlockDetectionTaskScheduler newTaskScheduler = new DeadlockDetectionTaskScheduler();
+ defaultTaskScheduler.SetValue(null, newTaskScheduler);
+
+ s_threadDueTimes = new ConcurrentDictionary();
+ s_deadlockWatchdog = new Timer(CheckForDeadlocks, null, _watchdogIntervalMs, _watchdogIntervalMs);
+
+ s_isEnabled = true;
+ }
+
+ ///
+ /// Adds the current Task execution thread to the tracked thread collection.
+ ///
+ public static void AddTaskThread()
+ {
+ if (s_isEnabled)
+ {
+ long dueTime = DateTime.UtcNow.Ticks + TaskThreadDeadlockTimeoutTicks;
+ AddThread(dueTime);
+ }
+ }
+
+ ///
+ /// Adds the current Test execution thread (i.e. a thread that is directly executing a [StressTest] method) to the tracked thread collection.
+ ///
+ public static void AddTestThread()
+ {
+ if (s_isEnabled)
+ {
+ long dueTime = DateTime.UtcNow.Ticks + TestThreadDeadlockTimeoutTicks;
+ AddThread(dueTime);
+ }
+ }
+
+ private static void AddThread(long dueTime)
+ {
+ s_threadDueTimes.TryAdd(Thread.CurrentThread, new ThreadInfo(dueTime));
+ }
+
+ ///
+ /// Removes the current thread from the tracked thread collection
+ ///
+ public static void RemoveThread()
+ {
+ if (s_isEnabled)
+ {
+ ThreadInfo unused;
+ s_threadDueTimes.TryRemove(Thread.CurrentThread, out unused);
+ }
+ }
+
+ ///
+ /// Disables abort of current thread. Call this when the current thread is waiting on a task.
+ ///
+ public static void DisableThreadAbort()
+ {
+ if (s_isEnabled)
+ {
+ ThreadInfo threadInfo;
+ if (s_threadDueTimes.TryGetValue(Thread.CurrentThread, out threadInfo))
+ {
+ threadInfo.DisableAbort = true;
+ threadInfo.DisableAbortTime = DateTime.UtcNow.Ticks;
+ }
+ }
+ }
+
+ ///
+ /// Enables abort of current thread after calling DisableThreadAbort(). The elapsed time since calling DisableThreadAbort() is added to the due time.
+ ///
+ public static void EnableThreadAbort()
+ {
+ if (s_isEnabled)
+ {
+ ThreadInfo threadInfo;
+ if (s_threadDueTimes.TryGetValue(Thread.CurrentThread, out threadInfo))
+ {
+ threadInfo.DueTime += DateTime.UtcNow.Ticks - threadInfo.DisableAbortTime;
+ threadInfo.DisableAbort = false;
+ }
+ }
+ }
+
+ ///
+ /// Looks through the tracked thread collection and aborts any thread that is past its due time
+ ///
+ /// unused
+ private static void CheckForDeadlocks(object state)
+ {
+ if (s_isEnabled)
+ {
+ long now = DateTime.UtcNow.Ticks;
+
+ // Find candidate threads
+ foreach (var threadDuePair in s_threadDueTimes)
+ {
+ if (!threadDuePair.Value.DisableAbort && now > threadDuePair.Value.DueTime)
+ {
+ // Abort the misbehaving thread and the return
+ // NOTE: We only want to abort a single thread at a time to allow the other thread in the deadlock pair to continue
+ Thread t = threadDuePair.Key;
+ Console.WriteLine("Deadlock detected on thread with managed thread id {0}", t.ManagedThreadId);
+ Debugger.Break();
+ t.Join();
+ return;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/DeadlockDetectionTaskScheduler.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/DeadlockDetectionTaskScheduler.cs
new file mode 100644
index 0000000000..22a540def8
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/DeadlockDetectionTaskScheduler.cs
@@ -0,0 +1,93 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace DPStressHarness
+{
+ public class DeadlockDetectionTaskScheduler : TaskScheduler
+ {
+ private readonly WaitCallback _runTaskCallback;
+ private readonly ParameterizedThreadStart _runTaskThreadStart;
+#if DEBUG
+ private readonly ConcurrentDictionary _queuedItems = new ConcurrentDictionary();
+#endif
+
+ public DeadlockDetectionTaskScheduler()
+ {
+ _runTaskCallback = new WaitCallback(RunTask);
+ _runTaskThreadStart = new ParameterizedThreadStart(RunTask);
+ }
+
+ // This is only used for debugging, so for retail we'd prefer the perf
+ protected override IEnumerable GetScheduledTasks()
+ {
+#if DEBUG
+ return _queuedItems.Keys;
+#else
+ return new Task[0];
+#endif
+ }
+
+ protected override void QueueTask(Task task)
+ {
+ if ((task.CreationOptions & TaskCreationOptions.LongRunning) == TaskCreationOptions.LongRunning)
+ {
+ // Create a new background thread for long running tasks
+ Thread thread = new Thread(_runTaskThreadStart) { IsBackground = true };
+ thread.Start(task);
+ }
+ else
+ {
+ // Otherwise queue the work on the threadpool
+#if DEBUG
+ _queuedItems.TryAdd(task, null);
+#endif
+
+ ThreadPool.QueueUserWorkItem(_runTaskCallback, task);
+ }
+ }
+
+ protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
+ {
+ if (!taskWasPreviouslyQueued)
+ {
+ // Run the task inline
+ RunTask(task);
+ return true;
+ }
+
+ // Couldn't run the task
+ return false;
+ }
+
+ private void RunTask(object state)
+ {
+ Task inTask = state as Task;
+
+#if DEBUG
+ // Remove from the dictionary of queued items
+ object ignored;
+ _queuedItems.TryRemove(inTask, out ignored);
+#endif
+
+ // Note when the thread started work
+ DeadlockDetection.AddTaskThread();
+
+ try
+ {
+ // Run the task
+ base.TryExecuteTask(inTask);
+ }
+ finally
+ {
+ // Remove the thread from the list when complete
+ DeadlockDetection.RemoveThread();
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/SqlClient.Stress.Common.csproj b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/SqlClient.Stress.Common.csproj
new file mode 100644
index 0000000000..5540d4951e
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/SqlClient.Stress.Common.csproj
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/TestMetrics.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/TestMetrics.cs
new file mode 100644
index 0000000000..054a822dc1
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/TestMetrics.cs
@@ -0,0 +1,368 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace DPStressHarness
+{
+ public static class TestMetrics
+ {
+ private const string _defaultValue = "unknown";
+
+ private static bool s_valid = false;
+ private static bool s_reset = true;
+ private static Stopwatch s_stopwatch = new Stopwatch();
+ private static long s_workingSet;
+ private static long s_peakWorkingSet;
+ private static long s_privateBytes;
+ private static Assembly s_targetAssembly;
+ private static string s_fileVersion = _defaultValue;
+ private static string s_privateBuild = _defaultValue;
+ private static string s_runLabel = DateTime.Now.ToString();
+ private static Dictionary s_overrides;
+ private static List s_variations = null;
+ private static List s_selectedTests = null;
+ private static bool s_isOfficial = false;
+ private static string s_milestone = _defaultValue;
+ private static string s_branch = _defaultValue;
+ private static List s_categories = null;
+ private static bool s_profileMeasuredCode = false;
+ private static int s_stressThreads = 16;
+ private static int s_stressDuration = 1;
+ private static int? s_exceptionThreshold = null;
+ private static bool s_monitorenabled = false;
+ private static string s_monitormachinename = "localhost";
+ private static int s_randomSeed = 0;
+ private static string s_filter = null;
+ private static bool s_printMethodName = false;
+
+ /// Starts the sample profiler.
+ ///
+ /// Do not inline to avoid errors when the functionality is not used
+ /// and the profiling DLL is not available.
+ ///
+ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
+ private static void InternalStartProfiling()
+ {
+ // Microsoft.VisualStudio.Profiler.DataCollection.StartProfile(
+ // Microsoft.VisualStudio.Profiler.ProfileLevel.Global,
+ // Microsoft.VisualStudio.Profiler.DataCollection.CurrentId);
+ }
+
+ /// Stops the sample profiler.
+ ///
+ /// Do not inline to avoid errors when the functionality is not used
+ /// and the profiling DLL is not available.
+ ///
+ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
+ private static void InternalStopProfiling()
+ {
+ // Microsoft.VisualStudio.Profiler.DataCollection.StopProfile(
+ // Microsoft.VisualStudio.Profiler.ProfileLevel.Global,
+ // Microsoft.VisualStudio.Profiler.DataCollection.CurrentId);
+ }
+
+ public static void StartCollection()
+ {
+ s_valid = false;
+
+ s_stopwatch.Reset();
+ s_stopwatch.Start();
+ s_reset = true;
+ }
+
+ public static void StartProfiling()
+ {
+ if (s_profileMeasuredCode)
+ {
+ InternalStartProfiling();
+ }
+ }
+
+ public static void StopProfiling()
+ {
+ if (s_profileMeasuredCode)
+ {
+ InternalStopProfiling();
+ }
+ }
+
+ public static void StopCollection()
+ {
+ s_stopwatch.Stop();
+
+ Process p = Process.GetCurrentProcess();
+ s_workingSet = p.WorkingSet64;
+ s_peakWorkingSet = p.PeakWorkingSet64;
+ s_privateBytes = p.PrivateMemorySize64;
+
+ s_valid = true;
+ }
+
+ public static void PauseTimer()
+ {
+ s_stopwatch.Stop();
+ }
+
+ public static void UnPauseTimer()
+ {
+ if (s_reset)
+ {
+ s_stopwatch.Reset();
+ s_reset = false;
+ }
+
+ s_stopwatch.Start();
+ }
+
+ private static void ThrowIfInvalid()
+ {
+ if (!s_valid) throw new InvalidOperationException("Collection must be stopped before accessing this metric.");
+ }
+
+ public static void Reset()
+ {
+ s_valid = false;
+ s_reset = true;
+ s_stopwatch = new Stopwatch();
+ s_workingSet = new long();
+ s_peakWorkingSet = new long();
+ s_privateBytes = new long();
+ s_targetAssembly = null;
+ s_fileVersion = _defaultValue;
+ s_privateBuild = _defaultValue;
+ s_runLabel = DateTime.Now.ToString();
+ s_overrides = null;
+ s_variations = null;
+ s_selectedTests = null;
+ s_isOfficial = false;
+ s_milestone = _defaultValue;
+ s_branch = _defaultValue;
+ s_categories = null;
+ s_profileMeasuredCode = false;
+ s_stressThreads = 16;
+ s_stressDuration = 1;
+ s_exceptionThreshold = null;
+ s_monitorenabled = false;
+ s_monitormachinename = "localhost";
+ s_randomSeed = 0;
+ s_filter = null;
+ s_printMethodName = false;
+ }
+
+ public static string FileVersion
+ {
+ get { return s_fileVersion; }
+ set { s_fileVersion = value; }
+ }
+
+ public static string PrivateBuild
+ {
+ get { return s_privateBuild; }
+ set { s_privateBuild = value; }
+ }
+
+ public static Assembly TargetAssembly
+ {
+ get { return s_targetAssembly; }
+
+ set
+ {
+ s_targetAssembly = value;
+ s_fileVersion = VersionUtil.GetFileVersion(s_targetAssembly.ManifestModule.FullyQualifiedName);
+ s_privateBuild = VersionUtil.GetPrivateBuild(s_targetAssembly.ManifestModule.FullyQualifiedName);
+ }
+ }
+
+ public static string RunLabel
+ {
+ get { return s_runLabel; }
+ set { s_runLabel = value; }
+ }
+
+ public static string Milestone
+ {
+ get { return s_milestone; }
+ set { s_milestone = value; }
+ }
+
+ public static string Branch
+ {
+ get { return s_branch; }
+ set { s_branch = value; }
+ }
+
+ public static bool IsOfficial
+ {
+ get { return s_isOfficial; }
+ set { s_isOfficial = value; }
+ }
+
+ public static bool IsDefaultValue(string val)
+ {
+ return val.Equals(_defaultValue);
+ }
+
+ public static double ElapsedSeconds
+ {
+ get
+ {
+ ThrowIfInvalid();
+ return s_stopwatch.ElapsedMilliseconds / 1000.0;
+ }
+ }
+
+ public static long WorkingSet
+ {
+ get
+ {
+ ThrowIfInvalid();
+ return s_workingSet;
+ }
+ }
+
+ public static long PeakWorkingSet
+ {
+ get
+ {
+ ThrowIfInvalid();
+ return s_peakWorkingSet;
+ }
+ }
+
+ public static long PrivateBytes
+ {
+ get
+ {
+ ThrowIfInvalid();
+ return s_privateBytes;
+ }
+ }
+
+
+ public static Dictionary Overrides
+ {
+ get
+ {
+ if (s_overrides == null)
+ {
+ s_overrides = new Dictionary(8);
+ }
+ return s_overrides;
+ }
+ }
+
+ public static List Variations
+ {
+ get
+ {
+ if (s_variations == null)
+ {
+ s_variations = new List(8);
+ }
+
+ return s_variations;
+ }
+ }
+
+ public static List SelectedTests
+ {
+ get
+ {
+ if (s_selectedTests == null)
+ {
+ s_selectedTests = new List(8);
+ }
+
+ return s_selectedTests;
+ }
+ }
+
+ public static bool IncludeTest(TestAttributeBase test)
+ {
+ if (s_selectedTests == null || s_selectedTests.Count == 0)
+ return true; // user has no selection - run all
+ else
+ return s_selectedTests.Contains(test.Title);
+ }
+
+ public static List Categories
+ {
+ get
+ {
+ if (s_categories == null)
+ {
+ s_categories = new List(8);
+ }
+
+ return s_categories;
+ }
+ }
+
+ public static bool ProfileMeasuredCode
+ {
+ get { return s_profileMeasuredCode; }
+ set { s_profileMeasuredCode = value; }
+ }
+
+ public static int StressDuration
+ {
+ get { return s_stressDuration; }
+ set { s_stressDuration = value; }
+ }
+
+ public static int StressThreads
+ {
+ get { return s_stressThreads; }
+ set { s_stressThreads = value; }
+ }
+
+ public static int? ExceptionThreshold
+ {
+ get { return s_exceptionThreshold; }
+ set { s_exceptionThreshold = value; }
+ }
+
+ public static bool MonitorEnabled
+ {
+ get { return s_monitorenabled; }
+ set
+ {
+ if(value)
+ {
+ throw new NotImplementedException($"The '{nameof(MonitorEnabled)}' isn't fully implemented!");
+ }
+ s_monitorenabled = value;
+ }
+ }
+
+
+ public static string MonitorMachineName
+ {
+ get { return s_monitormachinename; }
+ set { s_monitormachinename = value; }
+ }
+
+ public static int RandomSeed
+ {
+ get { return s_randomSeed; }
+ set { s_randomSeed = value; }
+ }
+
+ public static string Filter
+ {
+ get { return s_filter; }
+ set { s_filter = value; }
+ }
+
+ public static bool PrintMethodName
+ {
+ get { return s_printMethodName; }
+ set { s_printMethodName = value; }
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/VersionUtil.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/VersionUtil.cs
new file mode 100644
index 0000000000..1778903834
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/VersionUtil.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO;
+using System.Diagnostics;
+
+#pragma warning disable 618
+
+namespace DPStressHarness
+{
+ public class VersionUtil
+ {
+ public static string GetFileVersion(string moduleName)
+ {
+ FileVersionInfo info = GetFileVersionInfo(moduleName);
+ return info.FileVersion;
+ }
+
+ public static string GetPrivateBuild(string moduleName)
+ {
+ FileVersionInfo info = GetFileVersionInfo(moduleName);
+ return info.PrivateBuild;
+ }
+
+ private static FileVersionInfo GetFileVersionInfo(string moduleName)
+ {
+ if (File.Exists(moduleName))
+ {
+ return FileVersionInfo.GetVersionInfo(Path.GetFullPath(moduleName));
+ }
+ else
+ {
+ string moduleInRuntimeDir = AppContext.BaseDirectory + moduleName;
+ return FileVersionInfo.GetVersionInfo(moduleInRuntimeDir);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/AsyncUtils.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/AsyncUtils.cs
new file mode 100644
index 0000000000..84f0fba0de
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/AsyncUtils.cs
@@ -0,0 +1,185 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.Data.SqlClient;
+using System.Linq;
+using System.Runtime.ExceptionServices;
+using System.Threading.Tasks;
+using System.Xml;
+using DPStressHarness;
+
+namespace Stress.Data
+{
+ public enum SyncAsyncMode
+ {
+ Sync, // call sync method, e.g. connection.Open(), and return completed task
+ SyncOverAsync, // call async method, e.g. connection.OpenAsync().Wait(), and return completed task
+ Async // call async method, e.g. connection.OpenAsync(), and return running task
+ }
+
+ public static class AsyncUtils
+ {
+ public static Task SyncOrAsyncMethod(Func syncFunc, Func> asyncFunc, SyncAsyncMode mode)
+ {
+ switch (mode)
+ {
+ case SyncAsyncMode.Sync:
+ TResult result = syncFunc();
+ return Task.FromResult(result);
+
+ case SyncAsyncMode.SyncOverAsync:
+ Task t = asyncFunc();
+ WaitAndUnwrapException(t);
+ return t;
+
+ case SyncAsyncMode.Async:
+ return asyncFunc();
+
+ default:
+ throw new ArgumentException(mode.ToString());
+ }
+ }
+
+ public static Task SyncOrAsyncMethod(Action syncFunc, Func asyncFunc, SyncAsyncMode mode)
+ {
+ switch (mode)
+ {
+ case SyncAsyncMode.Sync:
+ syncFunc();
+ return Task.CompletedTask;
+
+ case SyncAsyncMode.SyncOverAsync:
+ Task t = asyncFunc();
+ WaitAndUnwrapException(t);
+ return t;
+
+ case SyncAsyncMode.Async:
+ return asyncFunc();
+
+ default:
+ throw new ArgumentException(mode.ToString());
+ }
+ }
+
+ public static void WaitAll(params Task[] ts)
+ {
+ DeadlockDetection.DisableThreadAbort();
+ try
+ {
+ Task.WaitAll(ts);
+ }
+ finally
+ {
+ DeadlockDetection.EnableThreadAbort();
+ }
+ }
+
+ public static void WaitAllNullable(params Task[] ts)
+ {
+ DeadlockDetection.DisableThreadAbort();
+ try
+ {
+ Task[] tasks = ts.Where(t => t != null).ToArray();
+ Task.WaitAll(tasks);
+ }
+ finally
+ {
+ DeadlockDetection.EnableThreadAbort();
+ }
+ }
+
+ public static void WaitAndUnwrapException(Task t)
+ {
+ DeadlockDetection.DisableThreadAbort();
+ try
+ {
+ t.Wait();
+ }
+ catch (AggregateException ae)
+ {
+ // The callers of this API may not expect AggregateException, so throw the inner exception
+ // If AggregateException contains more than one InnerExceptions, throw it out as it is,
+ // because that is unexpected
+ if ((ae.InnerExceptions != null) && (ae.InnerExceptions.Count == 1))
+ {
+ if (ae.InnerException != null)
+ {
+ ExceptionDispatchInfo info = ExceptionDispatchInfo.Capture(ae.InnerException);
+ info.Throw();
+ }
+ }
+
+ throw;
+ }
+ finally
+ {
+ DeadlockDetection.EnableThreadAbort();
+ }
+ }
+
+ public static T GetResult(IAsyncResult result)
+ {
+ return GetResult((Task)result);
+ }
+
+ public static T GetResult(Task result)
+ {
+ DeadlockDetection.DisableThreadAbort();
+ try
+ {
+ return result.Result;
+ }
+ finally
+ {
+ DeadlockDetection.EnableThreadAbort();
+ }
+ }
+
+ public static SqlDataReader ExecuteReader(SqlCommand command)
+ {
+ DeadlockDetection.DisableThreadAbort();
+ try
+ {
+ return command.ExecuteReader();
+ }
+ finally
+ {
+ DeadlockDetection.EnableThreadAbort();
+ }
+ }
+
+ public static int ExecuteNonQuery(SqlCommand command)
+ {
+ DeadlockDetection.DisableThreadAbort();
+ try
+ {
+ return command.ExecuteNonQuery();
+ }
+ finally
+ {
+ DeadlockDetection.DisableThreadAbort();
+ }
+ }
+
+ public static XmlReader ExecuteXmlReader(SqlCommand command)
+ {
+ DeadlockDetection.DisableThreadAbort();
+ try
+ {
+ return command.ExecuteXmlReader();
+ }
+ finally
+ {
+ DeadlockDetection.EnableThreadAbort();
+ }
+ }
+
+ public static SyncAsyncMode ChooseSyncAsyncMode(Random rnd)
+ {
+ // Any mode is allowed
+ return (SyncAsyncMode)rnd.Next(3);
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataSource.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataSource.cs
new file mode 100644
index 0000000000..b61379aa0a
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataSource.cs
@@ -0,0 +1,192 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+
+namespace Stress.Data
+{
+ ///
+ /// supported source types - values for 'type' attribute for 'source' node in App.config
+ ///
+ public enum DataSourceType
+ {
+ SqlServer
+ }
+
+ ///
+ /// base class for database source information (SQL Server, Oracle Server, Access Database file, etc...).
+ /// Data sources are loaded from the app config file.
+ ///
+ public abstract class DataSource
+ {
+ ///
+ /// name of the source - can be used in command line: StressTest ... -override source "sourcename"
+ ///
+ public readonly string Name;
+
+ ///
+ /// database type
+ ///
+ public readonly DataSourceType Type;
+
+ ///
+ /// whether this source is the default one for the type specified
+ ///
+ public readonly bool IsDefault;
+
+ ///
+ /// constructs new data source - called by derived class c-tors only (thus protected)
+ ///
+ protected DataSource(string name, DataSourceType type, bool isDefault)
+ {
+ this.Name = name;
+ this.Type = type;
+ this.IsDefault = isDefault;
+ }
+
+ ///
+ /// this method is used to create the data source, based on its type
+ ///
+ public static DataSource Create(string name, DataSourceType sourceType, bool isDefault, IDictionary properties)
+ {
+ switch (sourceType)
+ {
+ case DataSourceType.SqlServer:
+ return new SqlServerDataSource(name, isDefault, properties);
+ default:
+ throw new ArgumentException("Wrong source type value: " + sourceType);
+ }
+ }
+
+ ///
+ /// used by GetRequiredAttributeValue or derived classes to construct exception on missing required attribute
+ ///
+ /// name of the source (from XML) to include in exception message (for troubleshooting)
+ protected Exception MissingAttributeValueException(string sourceName, string attributeName)
+ {
+ return new ArgumentException(string.Format("Missing or empty value for {0} attribute in the config file for source: {1}", attributeName, sourceName));
+ }
+
+ ///
+ /// search for required attribute or fail if not found
+ ///
+ protected string GetRequiredAttributeValue(string sourceName, IDictionary properties, string valueName, bool allowEmpty)
+ {
+ string value;
+ if (!properties.TryGetValue(valueName, out value) || (value == null) || (!allowEmpty && value.Length == 0))
+ {
+ throw MissingAttributeValueException(sourceName, valueName);
+ }
+ return value;
+ }
+
+ ///
+ /// search for optional attribute or return default vale
+ ///
+ protected string GetOptionalAttributeValue(IDictionary properties, string valueName, string defaultValue)
+ {
+ string value;
+ if (!properties.TryGetValue(valueName, out value) || (value == null))
+ {
+ value = defaultValue;
+ }
+ return value;
+ }
+
+ public abstract void Emit(byte indent);
+ }
+
+ ///
+ /// Represents SQL Server data source. This source is used by SqlClient as well as by ODBC and OLEDB when connecting to SQL with SNAC or MDAC/WDAC
+ ///
+ ///
+ ///
+ ///
+ ///
+ public class SqlServerDataSource : DataSource
+ {
+ public readonly string DataSource;
+ public readonly string Database = "StressTests-" + Guid.NewGuid().ToString();
+ public readonly bool IsLocal;
+ public readonly bool Encrypt;
+
+ // If EntraIdUser is set, the connection will use EntraID password-based
+ // authentication.
+ public readonly string EntraIdUser;
+ public readonly string EntraIdPassword;
+
+ // If EntraIdUser isn't set, and User is set, the connection will use
+ // classic SQL user/password based authentication.
+ public readonly string User;
+ public readonly string Password;
+
+ // if true, test can create connnection strings with integrated security (trusted connection) set to true (or SSPI).
+ public readonly bool SupportsWindowsAuthentication;
+
+ public bool DisableMultiSubnetFailoverSetup;
+
+ public bool DisableNamedPipes;
+
+ internal SqlServerDataSource(string name, bool isDefault, IDictionary properties)
+ : base(name, DataSourceType.SqlServer, isDefault)
+ {
+ this.DataSource = GetOptionalAttributeValue(properties, "dataSource", "localhost");
+
+ this.EntraIdUser = GetOptionalAttributeValue(properties, "entraIdUser", string.Empty);
+ this.EntraIdPassword = GetOptionalAttributeValue(properties, "entraIdPassword", string.Empty);
+
+ this.User = GetOptionalAttributeValue(properties, "user", string.Empty);
+ this.Password = GetOptionalAttributeValue(properties, "password", string.Empty);
+
+ this.IsLocal = bool.Parse(GetOptionalAttributeValue(properties, "isLocal", bool.FalseString));
+ this.Encrypt = bool.Parse(GetOptionalAttributeValue(properties, "encrypt", bool.FalseString));
+
+ this.DisableMultiSubnetFailoverSetup = bool.Parse(GetOptionalAttributeValue(properties, "DisableMultiSubnetFailoverSetup", bool.TrueString));
+
+ this.DisableNamedPipes = bool.Parse(GetOptionalAttributeValue(properties, "DisableNamedPipes", bool.TrueString));
+
+ string temp = GetOptionalAttributeValue(properties, "supportsWindowsAuthentication", "false");
+ if (!string.IsNullOrEmpty(temp))
+ SupportsWindowsAuthentication = Convert.ToBoolean(temp);
+ else
+ SupportsWindowsAuthentication = false;
+
+ if (string.IsNullOrEmpty(EntraIdUser)
+ && string.IsNullOrEmpty(User)
+ && !SupportsWindowsAuthentication)
+ {
+ throw new ArgumentException("SQL Server settings should include either a valid user or SupportsWindowsAuthentication=true");
+ }
+ }
+
+ public override void Emit(byte indent)
+ {
+ string ind = new(' ', indent);
+ Console.WriteLine($"{ind}SqlServerDataSource:");
+ ind = new(' ', indent + 2);
+ Console.WriteLine($"{ind}Name: {Name}");
+ Console.WriteLine($"{ind}Type: {Type}");
+ Console.WriteLine($"{ind}IsDefault: {IsDefault}");
+ Console.WriteLine($"{ind}DataSource: {DataSource}");
+ Console.WriteLine($"{ind}Database: {Database}");
+ Console.WriteLine($"{ind}EntraIdUser: {EntraIdUser}");
+ Console.WriteLine($"{ind}EntraIdPassword: {new string('*', EntraIdPassword.Length)}");
+ Console.WriteLine($"{ind}User: {User}");
+ Console.WriteLine($"{ind}Password: {new string('*', Password.Length)}");
+ Console.WriteLine($"{ind}WinAuth: {SupportsWindowsAuthentication}");
+ Console.WriteLine($"{ind}IsLocal: {IsLocal}");
+ Console.WriteLine($"{ind}Encrypt: {Encrypt}");
+ Console.WriteLine($"{ind}DisableMultiSubnet: {DisableMultiSubnetFailoverSetup}");
+ Console.WriteLine($"{ind}DisableNamedPipes: {DisableNamedPipes}");
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressConnection.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressConnection.cs
new file mode 100644
index 0000000000..46becd4897
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressConnection.cs
@@ -0,0 +1,232 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Data.Common;
+using Microsoft.Data.SqlClient;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Stress.Data
+{
+ public class DataStressConnection : IDisposable
+ {
+ public DbConnection DbConnection { get; private set; }
+ private readonly bool _clearPoolBeforeClose;
+ public DataStressConnection(DbConnection conn, bool clearPoolBeforeClose = false)
+ {
+ if (conn == null)
+ throw new ArgumentException("Cannot pass in null DbConnection to make new DataStressConnection!");
+ this.DbConnection = conn;
+ _clearPoolBeforeClose = clearPoolBeforeClose;
+ }
+
+ private short _spid = 0;
+
+ [ThreadStatic]
+ private static TrackedRandom t_randomInstance;
+ private static TrackedRandom RandomInstance
+ {
+ get
+ {
+ if (t_randomInstance == null)
+ t_randomInstance = new TrackedRandom();
+ return t_randomInstance;
+ }
+ }
+
+ public void Open()
+ {
+ bool sync = RandomInstance.NextBool();
+
+ if (sync)
+ {
+ OpenSync();
+ }
+ else
+ {
+ Task t = OpenAsync();
+ AsyncUtils.WaitAndUnwrapException(t);
+ }
+ }
+
+ public async Task OpenAsync()
+ {
+ int startMilliseconds = Environment.TickCount;
+ try
+ {
+ await DbConnection.OpenAsync();
+ }
+ catch (ObjectDisposedException e)
+ {
+ HandleObjectDisposedException(e, true);
+ throw;
+ }
+ catch (InvalidOperationException e)
+ {
+ int endMilliseconds = Environment.TickCount;
+
+ // we may be able to handle this exception
+ HandleInvalidOperationException(e, startMilliseconds, endMilliseconds, true);
+ throw;
+ }
+
+ GetSpid();
+ }
+
+ private void OpenSync()
+ {
+ int startMilliseconds = Environment.TickCount;
+ try
+ {
+ DbConnection.Open();
+ }
+ catch (ObjectDisposedException e)
+ {
+ HandleObjectDisposedException(e, false);
+ throw;
+ }
+ catch (InvalidOperationException e)
+ {
+ int endMilliseconds = Environment.TickCount;
+
+ // we may be able to handle this exception
+ HandleInvalidOperationException(e, startMilliseconds, endMilliseconds, false);
+ throw;
+ }
+
+ GetSpid();
+ }
+
+ private void HandleObjectDisposedException(ObjectDisposedException e, bool async)
+ {
+ // Race condition in DbConnectionFactory.TryGetConnection results in an ObjectDisposedException when calling OpenAsync on a non-pooled connection
+ string methodName = async ? "OpenAsync()" : "Open()";
+ throw DataStressErrors.ProductError(
+ "Hit ObjectDisposedException in SqlConnection." + methodName, e);
+ }
+
+ private static int s_fastTimeoutCountOpen; // number of times hit by SqlConnection.Open
+ private static int s_fastTimeoutCountOpenAsync; // number of times hit by SqlConnection.OpenAsync
+ private static readonly DateTime s_startTime = DateTime.Now;
+
+ private const int MaxFastTimeoutCountPerDay = 200;
+
+ ///
+ /// Handles InvalidOperationException generated from Open or OpenAsync calls.
+ /// For any other type of Exception, it simply returns
+ ///
+ private void HandleInvalidOperationException(InvalidOperationException e, int startMilliseconds, int endMilliseconds, bool async)
+ {
+ int elapsedMilliseconds = unchecked(endMilliseconds - startMilliseconds); // unchecked to handle overflow of Environment.TickCount
+
+ // Since InvalidOperationExceptions due to timeout can be caused by issues
+ // (e.g. network hiccup, server unavailable, etc) we need a heuristic to guess whether or not this exception
+ // should have happened or not.
+ bool wasTimeoutFromPool = (e.GetType() == typeof(InvalidOperationException)) &&
+ (e.Message.StartsWith("Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool"));
+
+ bool wasTooEarly = (elapsedMilliseconds < ((DbConnection.ConnectionTimeout - 5) * 1000));
+
+ if (wasTimeoutFromPool && wasTooEarly)
+ {
+ if (async)
+ Interlocked.Increment(ref s_fastTimeoutCountOpenAsync);
+ else
+ Interlocked.Increment(ref s_fastTimeoutCountOpen);
+ }
+ }
+
+ ///
+ /// Gets spid value.
+ ///
+ ///
+ /// If we want to kill the connection, we get its spid up front before the test case uses the connection. Otherwise if
+ /// we try to get the spid when KillConnection is called, then the connection could be in a bad state (e.g. enlisted in
+ /// aborted transaction, or has open datareader) and we will fail to get the spid. Also the randomization is put here
+ /// instead of in KillConnection because otherwise this method would execute a command for every single connection which
+ /// most of the time will not be used later.
+ ///
+ private void GetSpid()
+ {
+ if (DbConnection is SqlConnection && RandomInstance.Next(0, 20) == 0)
+ {
+ using (var cmd = DbConnection.CreateCommand())
+ {
+ cmd.CommandText = "select @@spid";
+ _spid = (short)cmd.ExecuteScalar();
+ }
+ }
+ else
+ {
+ _spid = 0;
+ }
+ }
+
+ ///
+ /// Kills the given connection using "kill [spid]" if the parameter is nonzero
+ ///
+ private void KillConnection()
+ {
+ DataStressErrors.Assert(_spid != 0, "Called KillConnection with spid != 0");
+
+ using (var killerConn = DataTestGroup.Factory.CreateConnection())
+ {
+ killerConn.Open();
+
+ using (var killerCmd = killerConn.CreateCommand())
+ {
+ killerCmd.CommandText = "begin try kill " + _spid + " end try begin catch end catch";
+ killerCmd.ExecuteNonQuery();
+ }
+ }
+ }
+
+ ///
+ /// Kills the given connection using "kill [spid]" if the parameter is nonzero
+ ///
+ /// a Task that is asynchronously killing the connection, or null if the connection is not being killed
+ public Task KillConnectionAsync()
+ {
+ if (_spid == 0)
+ return null;
+ else
+ return Task.Factory.StartNew(() => KillConnection());
+ }
+
+ public void Close()
+ {
+ if (_spid != 0)
+ {
+ KillConnection();
+
+ // Wait before putting the connection back in the pool, to ensure that
+ // the pool checks the connection the next time it is used.
+ Task.Delay(10).ContinueWith((t) => DbConnection.Close());
+ }
+ else
+ {
+ // If this is a SqlConnection, and it is a connection with a unique connection string that we will never use again,
+ // then call SqlConnection.ClearPool() before closing so that it is fully closed and does not waste client & server resources.
+ if (_clearPoolBeforeClose)
+ {
+ SqlConnection sqlConn = DbConnection as SqlConnection;
+ if (sqlConn != null) SqlConnection.ClearPool(sqlConn);
+ }
+
+ DbConnection.Close();
+ }
+ }
+
+ public void Dispose()
+ {
+ Close();
+ }
+
+ public DbCommand CreateCommand()
+ {
+ return DbConnection.CreateCommand();
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressErrors.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressErrors.cs
new file mode 100644
index 0000000000..46b7751d50
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressErrors.cs
@@ -0,0 +1,215 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+
+namespace Stress.Data
+{
+ public enum ErrorHandlingAction
+ {
+ // If you add an item here, remember to add it to all of the methods below
+ DebugBreak,
+ ThrowException
+ }
+
+ ///
+ /// Static class containing methods to report errors.
+ ///
+ /// The StressTest executor will eat exceptions that are thrown and write them out to the console. In theory these should all be
+ /// either harmless exceptions or product bugs, however at present there are a large number of test issues that will cause a flood
+ /// of exceptions. Therefore if something actually bad happens (e.g. a known product bug is hit due to regression, or a major test
+ /// programming error) this error would be easy to miss if it were reported just by throwing an exception. To solve this, we use
+ /// this class for structured & consistent handling of errors.
+ ///
+ public static class DataStressErrors
+ {
+ private static void DebugBreak(string message, Exception exception)
+ {
+ // Print out the error before breaking to make debugging easier
+ Console.WriteLine(message);
+ if (exception != null)
+ {
+ Console.WriteLine(exception);
+ }
+
+ Debugger.Break();
+ }
+
+ ///
+ /// Reports that a product bug has been hit. The action that will be taken is configurable in the .config file.
+ /// This can be used to check for regressions of known product bugs.
+ ///
+ /// A description of the product bug hit (e.g. title, bug number & database, more information)
+ /// The exception that was thrown that indicates a product bug, or null if the product bug was detected without
+ /// having thrown an exception
+ /// An exception that the caller should throw.
+ public static Exception ProductError(string description, Exception exception = null)
+ {
+ switch (DataStressSettings.Instance.ActionOnProductError)
+ {
+ case ErrorHandlingAction.DebugBreak:
+ DebugBreak("Hit product error: " + description, exception);
+ return new ProductErrorException(description, exception);
+
+ case ErrorHandlingAction.ThrowException:
+ return new ProductErrorException(description, exception);
+
+ default:
+ throw UnhandledCaseError(DataStressSettings.Instance.ActionOnProductError);
+ }
+ }
+
+ ///
+ /// Reports that a non-fatal test error has been hit. The action that will be taken is configurable in the .config file.
+ /// This should be used for test errors that do not prevent the test from running.
+ ///
+ /// A description of the error
+ /// The exception that was thrown that indicates an error, or null if the error was detected without
+ /// An exception that the caller should throw.
+ public static Exception TestError(string description, Exception exception = null)
+ {
+ switch (DataStressSettings.Instance.ActionOnTestError)
+ {
+ case ErrorHandlingAction.DebugBreak:
+ DebugBreak("Hit test error: " + description, exception);
+ return new TestErrorException(description, exception);
+
+ case ErrorHandlingAction.ThrowException:
+ return new TestErrorException(description, exception);
+
+ default:
+ throw UnhandledCaseError(DataStressSettings.Instance.ActionOnTestError);
+ }
+ }
+
+ ///
+ /// Reports that a programming error in the test code has occurred. The action that will be taken is configurable in the .config file.
+ /// This must strictly be used to report programming errors. It should not be in any way possible to see one of these errors unless
+ /// you make an incorrect change to the code, for example having an unhandled case in a switch statement.
+ ///
+ /// A description of the error
+ /// The exception that was thrown that indicates an error, or null if the error was detected without
+ /// having thrown an exception
+ /// An exception that the caller should throw.
+ private static Exception ProgrammingError(string description, Exception exception = null)
+ {
+ switch (DataStressSettings.Instance.ActionOnProgrammingError)
+ {
+ case ErrorHandlingAction.DebugBreak:
+ DebugBreak("Hit programming error: " + description, exception);
+ return new ProgrammingErrorException(description, exception);
+
+ case ErrorHandlingAction.ThrowException:
+ return new ProgrammingErrorException(description, exception);
+
+ default:
+ // If we are here then it's a programming error, but calling UnhandledCaseError here would cause an inifite loop.
+ goto case ErrorHandlingAction.DebugBreak;
+ }
+ }
+
+ ///
+ /// Reports that an unhandled case in a switch statement in the test code has occurred. The action that will be taken is configurable
+ /// as a programming error in the .config file. It should not be in any way possible to see one of these errors unless
+ /// you make an incorrect change to the test code, for example having an unhandled case in a switch statement.
+ ///
+ /// The value that was not handled in the switch statement
+ /// An exception that the caller should throw.
+ public static Exception UnhandledCaseError(T unhandledValue)
+ {
+ return ProgrammingError("Unhandled case in switch statement: " + unhandledValue);
+ }
+
+ ///
+ /// Asserts that a condition is true. If the condition is false then throws a ProgrammingError.
+ /// This must strictly be used to report programming errors. It should not be in any way possible to see one of these errors unless
+ /// you make an incorrect change to the code, for example having an unhandled case in a switch statement.
+ ///
+ /// A condition to assert
+ /// A description of the error
+ /// if the condition is false
+ public static void Assert(bool condition, string description)
+ {
+ if (!condition)
+ {
+ throw ProgrammingError(description);
+ }
+ }
+
+ ///
+ /// Reports that a fatal error has happened. This is an error that completely prevents the test from continuing,
+ /// for example a setup failure. Ordinary programming errors should not be handled by this method.
+ ///
+ /// A description of the error
+ /// An exception that the caller should throw.
+ public static Exception FatalError(string description)
+ {
+ Console.WriteLine("Fatal test error: {0}", description);
+ Debugger.Break(); // Give the user a chance to debug
+ Environment.FailFast("Fatal error. Exit.");
+ return new Exception(); // Caller should throw this to indicate to the compiler that any code after the call is unreachable
+ }
+
+ #region Exception types
+
+ // These exception types are provided so that they can be easily found in logs, i.e. just do a text search in the console
+ // output log for "ProductErrorException"
+
+ private class ProductErrorException : Exception
+ {
+ public ProductErrorException()
+ : base()
+ {
+ }
+
+ public ProductErrorException(string message)
+ : base(message)
+ {
+ }
+
+ public ProductErrorException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+ }
+
+ private class ProgrammingErrorException : Exception
+ {
+ public ProgrammingErrorException()
+ : base()
+ {
+ }
+
+ public ProgrammingErrorException(string message)
+ : base(message)
+ {
+ }
+
+ public ProgrammingErrorException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+ }
+
+ private class TestErrorException : Exception
+ {
+ public TestErrorException()
+ : base()
+ {
+ }
+
+ public TestErrorException(string message)
+ : base(message)
+ {
+ }
+
+ public TestErrorException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+ }
+ #endregion
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressFactory.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressFactory.cs
new file mode 100644
index 0000000000..8e06a1de89
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressFactory.cs
@@ -0,0 +1,955 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Data;
+using System.Data.Common;
+using System.Diagnostics;
+
+namespace Stress.Data
+{
+ ///
+ /// Base class to generate utility objects required for stress tests to run. For example: connection strings, command texts,
+ /// data tables and views, and other information
+ ///
+ public abstract class DataStressFactory : IDisposable
+ {
+ // This is the maximum number of rows, stress will operate on
+ public const int Depth = 100;
+
+ // A string value to be used for scalar data retrieval while constructing
+ // a select statement that retrieves multiple result sets.
+ public static readonly string LargeStringParam = new string('p', 2000);
+
+ // A temp table that when create puts the server session into a non-recoverable state until dropped.
+ private static readonly string s_tempTableName = string.Format("#stress_{0}", Guid.NewGuid().ToString("N"));
+
+ // The languages used for "SET LANGUAGE [language]" statements that modify the server session state. Let's
+ // keep error message readable so we're only using english languages.
+ private static string[] s_languages = new string[]
+ {
+ "English",
+ "British English",
+ };
+
+ public DbProviderFactory DbFactory { get; private set; }
+
+ protected DataStressFactory(DbProviderFactory factory)
+ {
+ DataStressErrors.Assert(factory != null, "Argument to DataStressFactory constructor is null");
+ this.DbFactory = factory;
+ }
+
+
+ public void Dispose()
+ {
+ GC.SuppressFinalize(this);
+ }
+
+ public abstract string GetParameterName(string pName);
+
+
+ public abstract bool PrimaryKeyValueIsRequired
+ {
+ get;
+ }
+
+ [Flags]
+ public enum SelectStatementOptions
+ {
+ UseNOLOCK = 0x1,
+
+ // keep last
+ Default = 0
+ }
+
+ #region PoolingStressMode
+
+ public enum PoolingStressMode
+ {
+ RandomizeConnectionStrings, // Use many different connection strings with the same identity, which will result in many DbConnectionPoolGroups each containing one DbConnectionPool
+ }
+
+ protected PoolingStressMode CurrentPoolingStressMode
+ {
+ get;
+ private set;
+ }
+
+ #endregion
+
+
+ ///
+ /// Creates a new connection and initializes it with random connection string generated from the factory's source
+ /// Note: if rnd is null, create a connection with minimal string required to connect to the target database
+ ///
+ /// Randomizes Connection Pool enablement, the application Name to randomize connection pool
+ ///
+ ///
+ public DataStressConnection CreateConnection(Random rnd = null, ConnectionStringOptions options = ConnectionStringOptions.Default)
+ {
+ // Determine connection options (connection string, identity, etc)
+ string connectionString = CreateBaseConnectionString(rnd, options);
+ bool clearPoolBeforeClose = false;
+
+ if (rnd != null)
+ {
+ // Connection string and/or identity are randomized
+
+ // We implement this using the Application Name field in the connection string since this field
+ // should not affect behaviour other than connection pooling, since all connections in a pool
+ // must have the exact same connection string (including Application Name)
+
+ if (rnd.NextBool(.1))
+ {
+ // Disable pooling
+ connectionString += ";Pooling=false;";
+ }
+ else if (rnd.NextBool(0.001))
+ {
+ // Use a unique Application Name to get a new connection from a new pool. We do this in order to
+ // stress the code that creates/deletes pools.
+ connectionString = string.Format("{0}; Pooling=true; Application Name=\"{1}\";", connectionString, GetRandomApplicationName());
+
+ // Tell DataStressConnection to call SqlConnection.ClearPool when closing the connection. This ensures
+ // we do not keep a large number of connections in the pool that we will never use again.
+ clearPoolBeforeClose = true;
+ }
+ else
+ {
+ switch (CurrentPoolingStressMode)
+ {
+ case PoolingStressMode.RandomizeConnectionStrings:
+ // Use one of the pre-generated Application Names in order to get a pooled connection with a randomized connection string
+ connectionString = string.Format("{0}; Pooling=true; Application Name=\"{1}\";", connectionString, _applicationNames[rnd.Next(_applicationNames.Count)]);
+ break;
+ default:
+ throw DataStressErrors.UnhandledCaseError(CurrentPoolingStressMode);
+ }
+ }
+ }
+
+ // All options have been determined, now create
+ DbConnection con = DbFactory.CreateConnection();
+ con.ConnectionString = connectionString;
+ return new DataStressConnection(con, clearPoolBeforeClose);
+ }
+
+ [Flags]
+ public enum ConnectionStringOptions
+ {
+ Default = 0,
+
+ // by default, MARS is disabled
+ EnableMars = 0x2,
+
+ // by default, MultiSubnetFailover is enabled
+ DisableMultiSubnetFailover = 0x8
+ }
+
+ ///
+ /// Creates a new connection string.
+ /// Note: if rnd is null, create minimal connection string required to connect to the target database (used during setup)
+ /// Otherwise, string is randomized to enable multiple pools.
+ ///
+ public abstract string CreateBaseConnectionString(Random rnd, ConnectionStringOptions options);
+
+ protected virtual int GetNumDifferentApplicationNames()
+ {
+ return DataStressSettings.Instance.NumberOfConnectionPools;
+ }
+
+ private string GetRandomApplicationName()
+ {
+ return Guid.NewGuid().ToString();
+ }
+
+
+ ///
+ /// Returns index of a random table
+ /// This will be used to narrow down memory leaks
+ /// related to specific tables.
+ ///
+ public TableMetadata GetRandomTable(Random rnd)
+ {
+ return TableMetadataList[rnd.Next(TableMetadataList.Count)];
+ }
+
+ ///
+ /// Returns a random command object
+ ///
+ public DbCommand GetCommand(Random rnd, TableMetadata table, DataStressConnection conn, bool query, bool isXml = false)
+ {
+ if (query)
+ {
+ return GetSelectCommand(rnd, table, conn, isXml);
+ }
+ else
+ {
+ // make sure arguments are correct
+ DataStressErrors.Assert(!isXml, "wrong usage of GetCommand: cannot create command with FOR XML that is not query");
+
+ int select = rnd.Next(4);
+ switch (select)
+ {
+ case 0:
+ return GetUpdateCommand(rnd, table, conn);
+ case 1:
+ return GetInsertCommand(rnd, table, conn);
+ case 2:
+ return GetDeleteCommand(rnd, table, conn);
+ default:
+ return GetSelectCommand(rnd, table, conn);
+ }
+ }
+ }
+
+ private DbCommand CreateCommand(Random rnd, DataStressConnection conn)
+ {
+ DbCommand cmd;
+ if (conn == null)
+ {
+ cmd = DbFactory.CreateCommand();
+ }
+ else
+ {
+ cmd = conn.CreateCommand();
+ }
+
+ if (rnd != null)
+ {
+ cmd.CommandTimeout = rnd.NextBool() ? 30 : 600;
+ }
+
+ return cmd;
+ }
+
+ ///
+ /// Returns a random SELECT command
+ ///
+ public DbCommand GetSelectCommand(Random rnd, TableMetadata tableMetadata, DataStressConnection conn, bool isXml = false)
+ {
+ DbCommand com = CreateCommand(rnd, conn);
+ StringBuilder cmdText = new StringBuilder();
+ cmdText.Append(GetSelectCommandForMultipleRows(rnd, com, tableMetadata, isXml));
+
+ // 33% of the time, we also want to add another batch to the select command to allow for
+ // multiple result sets.
+ if ((!isXml) && (rnd.Next(0, 3) == 0))
+ {
+ cmdText.Append(";").Append(GetSelectCommandForScalarValue(com));
+ }
+
+ if ((!isXml) && ShouldModifySession(rnd))
+ {
+ cmdText.Append(";").Append(GetRandomSessionModificationStatement(rnd));
+ }
+
+ com.CommandText = cmdText.ToString();
+ return com;
+ }
+
+ ///
+ /// Returns a SELECT command that retrieves data from a table
+ ///
+ private string GetSelectCommandForMultipleRows(Random rnd, DbCommand com, TableMetadata inputTable, bool isXml)
+ {
+ int rowcount = rnd.Next(Depth);
+
+ StringBuilder cmdText = new StringBuilder();
+ cmdText.Append("SELECT TOP ");
+ cmdText.Append(rowcount); //Jonfo added this to prevent table scan of 75k row tables
+ cmdText.Append(" PrimaryKey");
+
+ List columns = inputTable.Columns;
+ int colindex = rnd.Next(0, columns.Count);
+
+ for (int i = 0; i <= colindex; i++)
+ {
+ if (columns[i].ColumnName == "PrimaryKey") continue;
+ cmdText.Append(", ");
+ cmdText.Append(columns[i].ColumnName);
+ }
+
+ cmdText.Append(" FROM \"");
+ cmdText.Append(inputTable.TableName);
+ cmdText.Append("\" WITH(NOLOCK) WHERE PrimaryKey ");
+
+ // We randomly pick an operator from '>' or '=' to allow for randomization
+ // of possible rows returned by this query. This approach *may* help
+ // in reducing the likelihood of multiple threads accessing same rows.
+ // If multiple threads access same rows, there may be locking issues
+ // which may be avoided because of this randomization.
+ string op = rnd.NextBool() ? ">" : "=";
+ cmdText.Append(op).Append(" ");
+
+ string pName = GetParameterName("P0");
+ cmdText.Append(pName);
+
+ DbParameter param = DbFactory.CreateParameter();
+ param.ParameterName = pName;
+ param.Value = GetRandomPK(rnd, inputTable);
+ param.DbType = DbType.Int32;
+ com.Parameters.Add(param);
+
+ return cmdText.ToString();
+ }
+
+ ///
+ /// Returns a SELECT command that returns a single string parameter value.
+ ///
+ private string GetSelectCommandForScalarValue(DbCommand com)
+ {
+ string pName = GetParameterName("P1");
+ StringBuilder cmdText = new StringBuilder();
+
+ cmdText.Append("SELECT ").Append(pName);
+
+ DbParameter param = DbFactory.CreateParameter();
+ param.ParameterName = pName;
+ param.Value = LargeStringParam;
+ param.Size = LargeStringParam.Length;
+ param.DbType = DbType.String;
+ com.Parameters.Add(param);
+
+ return cmdText.ToString();
+ }
+
+ ///
+ /// Returns a random existing Primary Key value
+ ///
+ private int GetRandomPK(Random rnd, TableMetadata table)
+ {
+ using (DataStressConnection conn = CreateConnection())
+ {
+ conn.Open();
+
+ // This technique to get a random row comes from http://www.4guysfromrolla.com/webtech/042606-1.shtml
+ // When you set rowcount and then select into a scalar value, then the query is optimised so that
+ // just the last value is selected. So if n = ROWCOUNT then the query returns the n'th row.
+
+ int rowNumber = rnd.Next(Depth);
+
+ DbCommand com = conn.CreateCommand();
+ string cmdText = string.Format(
+ @"SET ROWCOUNT {0};
+ DECLARE @PK INT;
+ SELECT @PK = PrimaryKey FROM {1} WITH(NOLOCK)
+ SELECT @PK", rowNumber, table.TableName);
+
+ com.CommandText = cmdText;
+
+ object result = com.ExecuteScalarSyncOrAsync(CancellationToken.None, rnd).Result;
+ if (result == DBNull.Value)
+ {
+ throw DataStressErrors.TestError(string.Format("Table {0} returned DBNull for primary key", table.TableName));
+ }
+ else
+ {
+ int primaryKey = (int)result;
+ return primaryKey;
+ }
+ }
+ }
+
+ private DbParameter CreateRandomParameter(Random rnd, string prefix, TableColumn column)
+ {
+ DbParameter param = DbFactory.CreateParameter();
+
+ param.ParameterName = GetParameterName(prefix);
+
+ param.Value = GetRandomData(rnd, column);
+
+ return param;
+ }
+
+ ///
+ /// Returns a random UPDATE command
+ ///
+ public DbCommand GetUpdateCommand(Random rnd, TableMetadata table, DataStressConnection conn)
+ {
+ DbCommand com = CreateCommand(rnd, conn);
+
+ StringBuilder cmdText = new StringBuilder();
+ cmdText.Append("UPDATE \"");
+ cmdText.Append(table.TableName);
+ cmdText.Append("\" SET ");
+
+ List columns = table.Columns;
+ int numColumns = rnd.Next(2, columns.Count);
+ bool mostlyNull = rnd.NextBool(0.1); // 10% of rows have 90% chance of each column being null, in order to test nbcrow
+
+ for (int i = 0; i < numColumns; i++)
+ {
+ if (columns[i].ColumnName == "PrimaryKey") continue;
+ if (columns[i].ColumnName.ToUpper() == "TIMESTAMP_FLD") continue;
+
+ if (i > 1) cmdText.Append(", ");
+ cmdText.Append(columns[i].ColumnName);
+ cmdText.Append(" = ");
+
+ if (mostlyNull && rnd.NextBool(0.9))
+ {
+ cmdText.Append("NULL");
+ }
+ else
+ {
+ DbParameter param = CreateRandomParameter(rnd, string.Format("P{0}", (i + 1)), columns[i]);
+ cmdText.Append(param.ParameterName);
+ com.Parameters.Add(param);
+ }
+ }
+
+ cmdText.Append(" WHERE PrimaryKey = ");
+ string pName = GetParameterName("P0");
+ cmdText.Append(pName);
+ DbParameter keyParam = DbFactory.CreateParameter();
+ keyParam.ParameterName = pName;
+ keyParam.Value = GetRandomPK(rnd, table);
+ com.Parameters.Add(keyParam);
+
+ if (ShouldModifySession(rnd))
+ {
+ cmdText.Append(";").Append(GetRandomSessionModificationStatement(rnd));
+ }
+
+ com.CommandText = cmdText.ToString();
+ return com;
+ }
+
+ ///
+ /// Returns a random INSERT command
+ ///
+ public DbCommand GetInsertCommand(Random rnd, TableMetadata table, DataStressConnection conn)
+ {
+ DbCommand com = CreateCommand(rnd, conn);
+
+ StringBuilder cmdText = new StringBuilder();
+ cmdText.Append("INSERT INTO \"");
+ cmdText.Append(table.TableName);
+ cmdText.Append("\" (");
+
+ StringBuilder valuesText = new StringBuilder();
+ valuesText.Append(") VALUES (");
+
+ List columns = table.Columns;
+ int numColumns = rnd.Next(2, columns.Count);
+ bool mostlyNull = rnd.NextBool(0.1); // 10% of rows have 90% chance of each column being null, in order to test nbcrow
+
+ for (int i = 0; i < numColumns; i++)
+ {
+ if (columns[i].ColumnName.ToUpper() == "PRIMARYKEY") continue;
+
+ if (i > 1)
+ {
+ cmdText.Append(", ");
+ valuesText.Append(", ");
+ }
+
+ cmdText.Append(columns[i].ColumnName);
+
+ if (columns[i].ColumnName.ToUpper() == "TIMESTAMP_FLD")
+ {
+ valuesText.Append("DEFAULT"); // Cannot insert an explicit value in a timestamp field
+ }
+ else if (mostlyNull && rnd.NextBool(0.9))
+ {
+ valuesText.Append("NULL");
+ }
+ else
+ {
+ DbParameter param = CreateRandomParameter(rnd, string.Format("P{0}", i + 1), columns[i]);
+
+ valuesText.Append(param.ParameterName);
+ com.Parameters.Add(param);
+ }
+ }
+
+ // To deal databases that do not support auto-incremented columns (Oracle?)
+ // if (!columns["PrimaryKey"].AutoIncrement)
+ if (PrimaryKeyValueIsRequired)
+ {
+ DbParameter param = CreateRandomParameter(rnd, "P0", table.GetColumn("PrimaryKey"));
+ cmdText.Append(", PrimaryKey");
+ valuesText.Append(", ");
+ valuesText.Append(param.ParameterName);
+ com.Parameters.Add(param);
+ }
+
+ valuesText.Append(")");
+ cmdText.Append(valuesText);
+
+ if (ShouldModifySession(rnd))
+ {
+ cmdText.Append(";").Append(GetRandomSessionModificationStatement(rnd));
+ }
+
+ com.CommandText = cmdText.ToString();
+ return com;
+ }
+
+ ///
+ /// Returns a random DELETE command
+ ///
+ public DbCommand GetDeleteCommand(Random rnd, TableMetadata table, DataStressConnection conn)
+ {
+ DbCommand com = CreateCommand(rnd, conn);
+
+ StringBuilder cmdText = new StringBuilder();
+ cmdText.Append("DELETE FROM \"");
+
+ List columns = table.Columns;
+ string pName = GetParameterName("P0");
+ cmdText.Append(table.TableName);
+ cmdText.Append("\" WHERE PrimaryKey = ");
+ cmdText.Append(pName);
+
+ DbParameter param = DbFactory.CreateParameter();
+ param.ParameterName = pName;
+ param.Value = GetRandomPK(rnd, table);
+ com.Parameters.Add(param);
+
+ if (ShouldModifySession(rnd))
+ {
+ cmdText.Append(";").Append(GetRandomSessionModificationStatement(rnd));
+ }
+
+ com.CommandText = cmdText.ToString();
+ return com;
+ }
+
+ public bool ShouldModifySession(Random rnd)
+ {
+ // 33% of the time, we want to modify the user session on the server
+ return rnd.NextBool(.33);
+ }
+
+ ///
+ /// Returns a random statement that will modify the session on the server.
+ ///
+ public string GetRandomSessionModificationStatement(Random rnd)
+ {
+ string sessionStmt = null;
+ int select = rnd.Next(3);
+ switch (select)
+ {
+ case 0:
+ // Create a SET CONTEXT_INFO statement using a hex string of random data
+ StringBuilder sb = new StringBuilder("0x");
+ int count = rnd.Next(1, 129);
+ for (int i = 0; i < count; i++)
+ {
+ sb.AppendFormat("{0:x2}", (byte)rnd.Next(0, (int)(byte.MaxValue + 1)));
+ }
+ string contextInfoData = sb.ToString();
+ sessionStmt = string.Format("SET CONTEXT_INFO {0}", contextInfoData);
+ break;
+
+ case 1:
+ // Create or drop the temp table
+ sessionStmt = string.Format("IF OBJECT_ID('tempdb..{0}') IS NULL CREATE TABLE {0}(id INT) ELSE DROP TABLE {0}", s_tempTableName);
+ break;
+
+ default:
+ // Create a SET LANGUAGE statement
+ sessionStmt = string.Format("SET LANGUAGE N'{0}'", s_languages[rnd.Next(s_languages.Length)]);
+ break;
+ }
+ return sessionStmt;
+ }
+
+ ///
+ /// Returns random data
+ ///
+ public object GetRandomData(Random rnd, TableColumn column)
+ {
+ int length = column.MaxLength;
+ int maxTargetLength = (length > 255 || length == -1) ? 255 : length;
+
+ DbType dbType = GetDbType(column);
+ return GetRandomData(rnd, dbType, maxTargetLength);
+ }
+
+ private DbType GetDbType(TableColumn column)
+ {
+ switch (column.ColumnName)
+ {
+ case "bit_FLD": return DbType.Boolean;
+ case "tinyint_FLD": return DbType.Byte;
+ case "smallint_FLD": return DbType.Int16;
+ case "int_FLD": return DbType.Int32;
+ case "PrimaryKey": return DbType.Int32;
+ case "bigint_FLD": return DbType.Int64;
+ case "real_FLD": return DbType.Single;
+ case "float_FLD": return DbType.Double;
+ case "smallmoney_FLD": return DbType.Decimal;
+ case "money_FLD": return DbType.Decimal;
+ case "decimal_FLD": return DbType.Decimal;
+ case "numeric_FLD": return DbType.Decimal;
+ case "datetime_FLD": return DbType.DateTime;
+ case "smalldatetime_FLD": return DbType.DateTime;
+ case "datetime2_FLD": return DbType.DateTime2;
+ case "timestamp_FLD": return DbType.Binary;
+ case "date_FLD": return DbType.Date;
+ case "time_FLD": return DbType.Time;
+ case "datetimeoffset_FLD": return DbType.DateTimeOffset;
+ case "uniqueidentifier_FLD": return DbType.Guid;
+ case "sql_variant_FLD": return DbType.Object;
+ case "image_FLD": return DbType.Binary;
+ case "varbinary_FLD": return DbType.Binary;
+ case "binary_FLD": return DbType.Binary;
+ case "char_FLD": return DbType.String;
+ case "varchar_FLD": return DbType.String;
+ case "text_FLD": return DbType.String;
+ case "ntext_FLD": return DbType.String;
+ case "nvarchar_FLD": return DbType.String;
+ case "nchar_FLD": return DbType.String;
+ case "nvarcharmax_FLD": return DbType.String;
+ case "varbinarymax_FLD": return DbType.Binary;
+ case "varcharmax_FLD": return DbType.String;
+ case "xml_FLD": return DbType.Xml;
+ default: throw DataStressErrors.UnhandledCaseError(column.ColumnName);
+ }
+ }
+
+ protected virtual object GetRandomData(Random rnd, DbType dbType, int maxLength)
+ {
+ byte[] buffer;
+ switch (dbType)
+ {
+ case DbType.Boolean:
+ return (rnd.Next(2) == 0 ? false : true);
+ case DbType.Byte:
+ return rnd.Next(byte.MinValue, byte.MaxValue + 1);
+ case DbType.Int16:
+ return rnd.Next(short.MinValue, short.MaxValue + 1);
+ case DbType.Int32:
+ return (rnd.Next(2) == 0 ? int.MaxValue / rnd.Next(1, 3) : int.MinValue / rnd.Next(1, 3));
+ case DbType.Int64:
+ return (rnd.Next(2) == 0 ? long.MaxValue / rnd.Next(1, 3) : long.MinValue / rnd.Next(1, 3));
+ case DbType.Single:
+ return rnd.NextDouble() * (rnd.Next(2) == 0 ? float.MaxValue : float.MinValue);
+ case DbType.Double:
+ return rnd.NextDouble() * (rnd.Next(2) == 0 ? double.MaxValue : double.MinValue);
+ case DbType.Decimal:
+ return rnd.Next(short.MinValue, short.MaxValue + 1);
+ case DbType.DateTime:
+ case DbType.DateTime2:
+ return DateTime.Now;
+ case DbType.Date:
+ return DateTime.Now.Date;
+ case DbType.Time:
+ return DateTime.Now.TimeOfDay.ToString("c");
+ case DbType.DateTimeOffset:
+ return DateTimeOffset.Now;
+ case DbType.Guid:
+ buffer = new byte[16];
+ rnd.NextBytes(buffer);
+ return (new Guid(buffer));
+ case DbType.Object:
+ case DbType.Binary:
+ rnd.NextBytes(buffer = new byte[rnd.Next(1, maxLength)]);
+ return buffer;
+ case DbType.String:
+ case DbType.Xml:
+ string openTag = "";
+ string closeTag = "";
+ int tagLength = openTag.Length + closeTag.Length;
+
+ if (tagLength > maxLength)
+ {
+ // Case (1): tagLength > maxTargetLength
+ return "";
+ }
+ else
+ {
+ StringBuilder builder = new StringBuilder(maxLength);
+
+ builder.Append(openTag);
+
+ // The data is just a repeat of one character because to the managed provider
+ // it is only really the length that matters, not the content of the data
+ char characterToUse = (char)rnd.Next((int)'@', (int)'~'); // Choosing random characters in this range to avoid special
+ // xml chars like '<' or '&'
+ int numRepeats = rnd.Next(0, maxLength - tagLength); // Case (2): tagLength == maxTargetLength
+ // Case (3): tagLength < maxTargetLength <-- most common
+ builder.Append(characterToUse, numRepeats);
+
+ builder.Append(closeTag);
+
+ DataStressErrors.Assert(builder.Length <= maxLength, "Incorrect length of randomly generated string");
+
+ return builder.ToString();
+ }
+ default:
+ throw DataStressErrors.UnhandledCaseError(dbType);
+ }
+ }
+
+ #region Table information to be used by stress
+
+ // method used to create stress tables in the database
+ protected void BuildUserTables(List TableMetadataList)
+ {
+ string CreateTable1 =
+ "CREATE TABLE stress_test_table_1 (PrimaryKey int identity(1,1) primary key, int_FLD int, smallint_FLD smallint, real_FLD real, float_FLD float, decimal_FLD decimal(28,4), " +
+ "smallmoney_FLD smallmoney, bit_FLD bit, tinyint_FLD tinyint, uniqueidentifier_FLD uniqueidentifier, varbinary_FLD varbinary(756), binary_FLD binary(756), " +
+ "image_FLD image, varbinarymax_FLD varbinary(max), timestamp_FLD timestamp, char_FLD char(756), text_FLD text, varcharmax_FLD varchar(max), " +
+ "varchar_FLD varchar(756), nchar_FLD nchar(756), ntext_FLD ntext, nvarcharmax_FLD nvarchar(max), nvarchar_FLD nvarchar(756), datetime_FLD datetime, " +
+ "smalldatetime_FLD smalldatetime);" +
+ "CREATE UNIQUE INDEX stress_test_table_1 on stress_test_table_1 ( PrimaryKey );" +
+ "insert into stress_test_table_1(int_FLD, smallint_FLD, real_FLD, float_FLD, decimal_FLD, " +
+ "smallmoney_FLD, bit_FLD, tinyint_FLD, uniqueidentifier_FLD, varbinary_FLD, binary_FLD, " +
+ "image_FLD, varbinarymax_FLD, char_FLD, text_FLD, varcharmax_FLD, " +
+ "varchar_FLD, nchar_FLD, ntext_FLD, nvarcharmax_FLD, nvarchar_FLD, datetime_FLD, " +
+ "smalldatetime_FLD) values ( 0, 0, 0, 0, 0, $0, 0, 0, '00000000-0000-0000-0000-000000000000', " +
+ "0x00, 0x00, 0x00, 0x00, '0', '0', '0', '0', N'0', N'0', N'0', N'0', '01/11/2000 12:54:01', '01/11/2000 12:54:00' );"
+ ;
+
+ string CreateTable2 =
+ "CREATE TABLE stress_test_table_2 (PrimaryKey int identity(1,1) primary key, bigint_FLD bigint, money_FLD money, numeric_FLD numeric, " +
+ "time_FLD time, date_FLD date, datetimeoffset_FLD datetimeoffset, sql_variant_FLD sql_variant, " +
+ "datetime2_FLD datetime2, xml_FLD xml);" +
+ "CREATE UNIQUE INDEX stress_test_table_2 on stress_test_table_2 ( PrimaryKey );" +
+ "insert into stress_test_table_2(bigint_FLD, money_FLD, numeric_FLD, " +
+ "time_FLD, date_FLD, datetimeoffset_FLD, sql_variant_FLD, " +
+ "datetime2_FLD, xml_FLD) values ( 0, $0, 0, '01/11/2015 12:54:01', '01/11/2015 12:54:01', '01/11/2000 12:54:01 -08:00', 0, '01/11/2000 12:54:01', '0' );"
+ ;
+
+ if (TableMetadataList == null)
+ {
+ TableMetadataList = new List();
+ }
+
+ List tableColumns1 = new List();
+ tableColumns1.Add(new TableColumn("PrimaryKey", -1));
+ tableColumns1.Add(new TableColumn("int_FLD", -1));
+ tableColumns1.Add(new TableColumn("smallint_FLD", -1));
+ tableColumns1.Add(new TableColumn("real_FLD", -1));
+ tableColumns1.Add(new TableColumn("float_FLD", -1));
+ tableColumns1.Add(new TableColumn("decimal_FLD", -1));
+ tableColumns1.Add(new TableColumn("smallmoney_FLD", -1));
+ tableColumns1.Add(new TableColumn("bit_FLD", -1));
+ tableColumns1.Add(new TableColumn("tinyint_FLD", -1));
+ tableColumns1.Add(new TableColumn("uniqueidentifier_FLD", -1));
+ tableColumns1.Add(new TableColumn("varbinary_FLD", 756));
+ tableColumns1.Add(new TableColumn("binary_FLD", 756));
+ tableColumns1.Add(new TableColumn("image_FLD", -1));
+ tableColumns1.Add(new TableColumn("varbinarymax_FLD", -1));
+ tableColumns1.Add(new TableColumn("timestamp_FLD", -1));
+ tableColumns1.Add(new TableColumn("char_FLD", -1));
+ tableColumns1.Add(new TableColumn("text_FLD", -1));
+ tableColumns1.Add(new TableColumn("varcharmax_FLD", -1));
+ tableColumns1.Add(new TableColumn("varchar_FLD", 756));
+ tableColumns1.Add(new TableColumn("nchar_FLD", 756));
+ tableColumns1.Add(new TableColumn("ntext_FLD", -1));
+ tableColumns1.Add(new TableColumn("nvarcharmax_FLD", -1));
+ tableColumns1.Add(new TableColumn("nvarchar_FLD", 756));
+ tableColumns1.Add(new TableColumn("datetime_FLD", -1));
+ tableColumns1.Add(new TableColumn("smalldatetime_FLD", -1));
+ TableMetadata tableMeta1 = new TableMetadata("stress_test_table_1", tableColumns1);
+ TableMetadataList.Add(tableMeta1);
+
+ List tableColumns2 = new List();
+ tableColumns2.Add(new TableColumn("PrimaryKey", -1));
+ tableColumns2.Add(new TableColumn("bigint_FLD", -1));
+ tableColumns2.Add(new TableColumn("money_FLD", -1));
+ tableColumns2.Add(new TableColumn("numeric_FLD", -1));
+ tableColumns2.Add(new TableColumn("time_FLD", -1));
+ tableColumns2.Add(new TableColumn("date_FLD", -1));
+ tableColumns2.Add(new TableColumn("datetimeoffset_FLD", -1));
+ tableColumns2.Add(new TableColumn("sql_variant_FLD", -1));
+ tableColumns2.Add(new TableColumn("datetime2_FLD", -1));
+ tableColumns2.Add(new TableColumn("xml_FLD", -1));
+ TableMetadata tableMeta2 = new TableMetadata("stress_test_table_2", tableColumns2);
+ TableMetadataList.Add(tableMeta2);
+
+ using (DataStressConnection conn = CreateConnection(null))
+ {
+ conn.Open();
+ using (DbCommand com = conn.CreateCommand())
+ {
+ try
+ {
+ com.CommandText = CreateTable1;
+ com.ExecuteNonQuery();
+ }
+ catch (DbException de)
+ {
+ // This can be improved by doing a Drop Table if exists.
+ if (de.Message.Contains("There is already an object named \'" + tableMeta1.TableName + "\' in the database."))
+ {
+ CleanupUserTables(tableMeta1);
+ com.ExecuteNonQuery();
+ }
+ else
+ {
+ throw;
+ }
+ }
+
+ try
+ {
+ com.CommandText = CreateTable2;
+ com.ExecuteNonQuery();
+ }
+ catch (DbException de)
+ {
+ // This can be improved by doing a Drop Table if exists in the query itself.
+ if (de.Message.Contains("There is already an object named \'" + tableMeta2.TableName + "\' in the database."))
+ {
+ CleanupUserTables(tableMeta2);
+ com.ExecuteNonQuery();
+ }
+ else
+ {
+ throw;
+ }
+ }
+
+ for (int i = 0; i < Depth; i++)
+ {
+ TrackedRandom randomInstance = new TrackedRandom();
+ randomInstance.Mark();
+
+ DbCommand comInsert1 = GetInsertCommand(randomInstance, tableMeta1, conn);
+ comInsert1.ExecuteNonQuery();
+
+ DbCommand comInsert2 = GetInsertCommand(randomInstance, tableMeta2, conn);
+ comInsert2.ExecuteNonQuery();
+ }
+ }
+ }
+ }
+
+ // method used to delete stress tables in the database
+ protected void CleanupUserTables(TableMetadata tableMetadata)
+ {
+ string DropTable = "drop TABLE " + tableMetadata.TableName + ";";
+
+ using (DataStressConnection conn = CreateConnection(null))
+ {
+ conn.Open();
+ using (DbCommand com = conn.CreateCommand())
+ {
+ try
+ {
+ com.CommandText = DropTable;
+ com.ExecuteNonQuery();
+ }
+ catch (Exception) { }
+ }
+ }
+ }
+
+ public List TableMetadataList
+ {
+ get;
+ private set;
+ }
+
+ public class TableMetadata
+ {
+ private string _tableName;
+ private List _columns = new List();
+
+ public TableMetadata(string tbleName, List cols)
+ {
+ _tableName = tbleName;
+ _columns = cols;
+ }
+
+ public string TableName
+ {
+ get { return _tableName; }
+ }
+
+ public List Columns
+ {
+ get { return _columns; }
+ }
+
+ public TableColumn GetColumn(string colName)
+ {
+ foreach (TableColumn column in _columns)
+ {
+ if (column.ColumnName.Equals(colName))
+ {
+ return column;
+ }
+ }
+ return null;
+ }
+ }
+
+ public class TableColumn
+ {
+ private string _columnName;
+ private int _maxLength;
+
+ public TableColumn(string colName, int maxLen)
+ {
+ _columnName = colName;
+ _maxLength = maxLen;
+ }
+
+ public string ColumnName
+ {
+ get { return _columnName; }
+ }
+
+ public int MaxLength
+ {
+ get { return _maxLength; }
+ }
+ }
+
+ private List _applicationNames;
+
+ ///
+ /// Gets schema of all tables from the back-end database and fills
+ /// the m_Tables DataSet with this schema. This DataSet is used to
+ /// generate random command text for tests.
+ ///
+ public void InitializeSharedData(DataSource source)
+ {
+ Trace.WriteLine("Creating shared objects", this.ToString());
+
+ // Initialize m_sharedDataSet
+ TableMetadataList = new List();
+ BuildUserTables(TableMetadataList);
+
+ // Initialize m_applicationNames
+ _applicationNames = new List();
+ for (int i = 0; i < GetNumDifferentApplicationNames(); i++)
+ {
+ _applicationNames.Add(GetRandomApplicationName());
+ }
+
+ // Initialize CurrentPoolingStressMode
+ CurrentPoolingStressMode = PoolingStressMode.RandomizeConnectionStrings;
+
+
+ Trace.WriteLine("Finished creating shared objects", this.ToString());
+ }
+
+ public void CleanupSharedData()
+ {
+ foreach (TableMetadata meta in TableMetadataList)
+ {
+ CleanupUserTables(meta);
+ }
+ TableMetadataList = null;
+ }
+
+ public abstract void CreateDatabase(DataSource source);
+ public abstract void DropDatabase(DataSource source);
+
+ #endregion
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressReader.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressReader.cs
new file mode 100644
index 0000000000..cad1bfa579
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressReader.cs
@@ -0,0 +1,350 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Xml;
+using System.Data;
+using System.Data.Common;
+using Microsoft.Data.SqlClient;
+using System.Data.SqlTypes;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Stress.Data
+{
+ public class DataStressReader : IDisposable
+ {
+ #region Type method mapping
+
+ private static Dictionary>> s_sqlTypes;
+ private static Dictionary>> s_clrTypes;
+
+ static DataStressReader()
+ {
+ InitSqlTypes();
+ InitClrTypes();
+ }
+
+ private static void InitSqlTypes()
+ {
+ s_sqlTypes = new Dictionary>>();
+
+ s_sqlTypes.Add(typeof(SqlBinary), (reader, ordinal, token, rnd) => reader.GetFieldValueSyncOrAsync(ordinal, token, rnd));
+ s_sqlTypes.Add(typeof(SqlBoolean), (reader, ordinal, token, rnd) => reader.GetFieldValueSyncOrAsync(ordinal, token, rnd));
+ s_sqlTypes.Add(typeof(SqlByte), (reader, ordinal, token, rnd) => reader.GetFieldValueSyncOrAsync(ordinal, token, rnd));
+ s_sqlTypes.Add(typeof(SqlBytes), (reader, ordinal, token, rnd) => reader.GetFieldValueSyncOrAsync(ordinal, token, rnd));
+ s_sqlTypes.Add(typeof(SqlChars), (reader, ordinal, token, rnd) => reader.GetFieldValueSyncOrAsync(ordinal, token, rnd));
+ s_sqlTypes.Add(typeof(SqlDateTime), (reader, ordinal, token, rnd) => reader.GetFieldValueSyncOrAsync(ordinal, token, rnd));
+ s_sqlTypes.Add(typeof(SqlDecimal), (reader, ordinal, token, rnd) => reader.GetFieldValueSyncOrAsync(ordinal, token, rnd));
+ s_sqlTypes.Add(typeof(SqlDouble), (reader, ordinal, token, rnd) => reader.GetFieldValueSyncOrAsync(ordinal, token, rnd));
+ s_sqlTypes.Add(typeof(SqlGuid), (reader, ordinal, token, rnd) => reader.GetFieldValueSyncOrAsync(ordinal, token, rnd));
+ s_sqlTypes.Add(typeof(SqlInt16), (reader, ordinal, token, rnd) => reader.GetFieldValueSyncOrAsync(ordinal, token, rnd));
+ s_sqlTypes.Add(typeof(SqlInt32), (reader, ordinal, token, rnd) => reader.GetFieldValueSyncOrAsync(ordinal, token, rnd));
+ s_sqlTypes.Add(typeof(SqlInt64), (reader, ordinal, token, rnd) => reader.GetFieldValueSyncOrAsync(ordinal, token, rnd));
+ s_sqlTypes.Add(typeof(SqlMoney), (reader, ordinal, token, rnd) => reader.GetFieldValueSyncOrAsync(ordinal, token, rnd));
+ s_sqlTypes.Add(typeof(SqlSingle), (reader, ordinal, token, rnd) => reader.GetFieldValueSyncOrAsync(ordinal, token, rnd));
+ s_sqlTypes.Add(typeof(SqlString), (reader, ordinal, token, rnd) => reader.GetFieldValueSyncOrAsync(ordinal, token, rnd));
+ s_sqlTypes.Add(typeof(SqlXml), (reader, ordinal, token, rnd) => reader.GetFieldValueSyncOrAsync(ordinal, token, rnd));
+ }
+
+ private static void InitClrTypes()
+ {
+ s_clrTypes = new Dictionary