diff --git a/.editorconfig b/.editorconfig index 19e2a3c9be..0543e5fd6e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -159,7 +159,7 @@ dotnet_diagnostic.xUnit1031.severity=none dotnet_diagnostic.xUnit1030.severity=none # Xml files -[*.{xml,csproj,stylecop,resx,ruleset,props,targets,config,nuspec}] +[*.{xml,slnx,proj,csproj,stylecop,resx,ruleset,props,targets,config,nuspec}] indent_size = 2 # Shell scripts diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index efeb747cad..178c3fc2d6 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -16,28 +16,33 @@ Once the environment is setup properly, execute the desired set of commands belo ### Targets +The following build targets are defined in `build.proj`: + |Target|Description| |-|-| |`BuildAllConfigurations`|Default target. Builds the .NET Framework and .NET drivers for all target frameworks and operating systems.| +|`BuildAbstractionsPackage`|Restore, build, and pack the Abstractions package, publishing the resulting NuGet into `packages/`.| |`BuildNetCore`|Builds the .NET driver for all target frameworks.| |`BuildNetCoreAllOS`|Builds the .NET driver for all target frameworks and operating systems.| |`BuildNetFx`|Builds the .NET Framework driver for all target frameworks.| |`BuildTestsNetCore`|Builds tests for the .NET driver.| |`BuildTestsNetFx`|Builds tests for the .NET Framework driver.| -|`Clean`|Cleans generated files.| -|`Restore`|Restores Nuget packages.| +|`Clean`|Cleans all generated files.| +|`Restore`|Restores NuGet packages.| |`RunTests`|Runs the unit, functional, and manual tests for the .NET Framework and .NET drivers| |`RunUnitTests`|Runs just the unit tests for the .NET Framework and .NET drivers| |`RunFunctionalTests`|Runs just the functional tests for the .NET Framework and .NET drivers| |`RunManualTests`|Runs just the manual tests for the .NET Framework and .NET drivers| |`BuildAkv`|Builds the Azure Key Vault Provider package for all supported platforms.| - ### Parameters + +The following parameters may be defined as MSBuild properties to configure the +build: + |Name|Supported Values|Default|Description| |-|-|-|-| |`Configuration`|`Debug`, `Release`|`Debug`|Sets the release configuration.| -|`BuildNetFx`|`true`, `false`|`true` (Windows), `false` (other)|If false, skips building the .NET Framework driver on Windows.| |`OSGroup`|`Unix`, `Windows_NT`, `AnyOS`|typically defaults to the client system's OS, unless using `BuildAllConfigurations` or an `AnyOS` specific target|The operating system to target.| |`Platform`|`AnyCPU`, `x86`, `x64`, `ARM`, `ARM64`|`AnyCPU`|May only be set when using package reference type or running tests.| |`TestSet`|`1`, `2`, `3`, `AE`|all|Build or run a subset of the manual tests. Omit (default) to target all tests.| @@ -45,11 +50,12 @@ Once the environment is setup properly, execute the desired set of commands belo |`TF`|`net8.0`, `net462`, `net47`, `net471`, `net472`, `net48`, `net481`|`net8.0` in netcore, `net462` in netfx|Sets the target framework when building or running tests. Not applicable when building the drivers.| |`ResultsDirectory`|An absolute file path|./TestResults relative to current directory|Specifies where to write test results.| - ## Example Workflows using MSBuild (Recommended) + Using the default configuration and running all tests: ```bash +msbuild -t:BuildAbstractionsPackage msbuild msbuild -t:BuildTestsNetFx -p:TF=net462 msbuild -t:BuildTestsNetCore @@ -59,28 +65,31 @@ msbuild -t:RunTests Using the Release configuration: ```bash -msbuild -p:configuration=Release -msbuild -t:BuildTestsNetFx -p:TF=net462 -p:configuration=Release -msbuild -t:BuildTestsNetCore -p:configuration=Release -msbuild -t:RunTests -p:configuration=Release +msbuild -t:BuildAbstractionsPackage -p:Configuration=Release +msbuild -p:Configuration=Release +msbuild -t:BuildTestsNetFx -p:TF=net462 -p:Configuration=Release +msbuild -t:BuildTestsNetCore -p:Configuration=Release +msbuild -t:RunTests -p:Configuration=Release ``` Running only the unit tests: ```bash +msbuild -t:BuildAbstractionsPackage msbuild msbuild -t:BuildTestsNetFx -p:TF=net462 msbuild -t:BuildTestsNetCore msbuild -t:RunUnitTests ``` -Using a specific dotnet version/architecture: +Using a specific .NET runtime to run tests: ```bash -msbuild -p:configuration=Release -msbuild -t:BuildTestsNetFx -p:TF=net462 -p:configuration=Release -msbuild -t:BuildTestsNetCore -p:configuration=Release -msbuild -t:RunTests -p:configuration=Release -p:DotnetPath=C:\net8-win-x86\ +msbuild -t:BuildAbstractionsPackage +msbuild +msbuild -t:BuildTestsNetFx -p:TF=net462 +msbuild -t:BuildTestsNetCore +msbuild -t:RunTests -p:DotnetPath=C:\net8-win-x86\ ``` ### Running Manual Tests @@ -119,15 +128,13 @@ Manual Tests require the below setup to run: |IsManagedInstance | (Optional) When set to `true` **TVP** related tests will use on non-Azure bs files to compare test results. this is needed when testing against Managed Instances or TVP Tests will fail on Test set 3. The default value is `false`. | |PowerShellPath | The full path to PowerShell.exe. This is not required if the path is present in the PATH environment variable. | `D:\\escaped\\absolute\\path\\to\\PowerShell.exe` | - ## Example workflows using the Dotnet SDK -#### Run Functional Tests +### Run Functional Tests - Windows (`netfx x86`): ```bash -msbuild dotnet test "src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.Tests.csproj" -p:Platform="x86" -p:Configuration="Release" -p:TestTargetOS="Windowsnetfx" --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" ``` @@ -152,7 +159,8 @@ dotnet test "src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.S ```bash dotnet test "src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj" -p:Platform="AnyCPU" -p:Configuration="Release" -p:TestTargetOS="Unixnetcoreapp" --no-build -v n --filter "category!=nonnetcoreapptests&category!=failing&category!=nonlinuxtests&category!=nonuaptests" ``` -#### Run Manual Tests + +### Run Manual Tests - Windows (`netfx x86`): @@ -194,35 +202,40 @@ dotnet test "src\Microsoft.Data.SqlClient\tests\ManualTests\Microsoft.Data.SqlCl Tests can be built and run with custom "Reference Type" property that enables different styles of testing: -- "Project" => Build and run tests with Microsoft.Data.SqlClient as Project Reference -- "Package" => Build and run tests with Microsoft.Data.SqlClient as Package Reference with configured "TestMicrosoftDataSqlClientVersion" in "Versions.props" file. +- "Project" => Build and run tests with Microsoft.Data.SqlClient as a Project Reference +- "Package" => Build and run tests with Microsoft.Data.SqlClient as a Package Reference with configured "TestMicrosoftDataSqlClientVersion" in "Versions.props" file. > ************** IMPORTANT NOTE BEFORE PROCEEDING WITH "PACKAGE" REFERENCE TYPE *************** > CREATE A NUGET PACKAGE WITH BELOW COMMAND AND ADD TO LOCAL FOLDER + UPDATE NUGET CONFIG FILE TO READ FROM THAT LOCATION > > ```bash -> msbuild -p:configuration=Release +> msbuild -t:BuildAbstractionsPackage -p:Configuration=Release +> msbuild -p:Configuration=Release > ``` A non-AnyCPU platform reference can only be used with package reference type. Otherwise, the specified platform will be replaced with AnyCPU in the build process. ### Building Tests with Reference Type -For .NET, all 4 reference types are supported: +For .NET: ```bash +# Project is the default reference type. The below commands are equivalent: +msbuild -t:BuildTestsNetCore msbuild -t:BuildTestsNetCore -p:ReferenceType=Project -# Default setting uses Project Reference. +# Package reference type: msbuild -t:BuildTestsNetCore -p:ReferenceType=Package ``` -For .NET Framework, below reference types are supported: +For .NET Framework: ```bash +# Project is the default reference type. The below commands are equivalent: +msbuild -t:BuildTestsNetFx -p:TF=net462 msbuild -t:BuildTestsNetFx -p:TF=net462 -p:ReferenceType=Project -# Default setting uses Project Reference. +# Package reference type: msbuild -t:BuildTestsNetFx -p:TF=net462 -p:ReferenceType=Package ``` @@ -241,26 +254,25 @@ Tests can be built and run with custom Target Frameworks. See the below examples ### Building Tests with custom target framework ```bash -msbuild -t:BuildTestsNetFx -p:TF=net462 # Build the tests for custom .NET Framework target +msbuild -t:BuildTestsNetFx -p:TF=net462 ``` ```bash -msbuild -t:BuildTestsNetCore -p:TF=net8.0 # Build the tests for custom .NET target +msbuild -t:BuildTestsNetCore -p:TF=net8.0 ``` ### Running Tests with custom target framework (traditional) ```bash +# Run tests with custom .NET Framework target dotnet test -p:TargetNetFxVersion=net462 ... -# Use above property to run Functional Tests with custom .NET Framework target +# Run tests with custom .NET target dotnet test -p:TargetNetCoreVersion=net8.0 ... -# Use above property to run Functional Tests with custom .NET target ``` - ## Using Managed SNI on Windows Managed SNI can be enabled on Windows by enabling the below AppContext switch: @@ -285,20 +297,6 @@ When connecting to a server, if a protocol lower than TLS 1.2 is negotiated, a s `Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning` -### Troubleshooting Docker issues - -There may be times where connection cannot be made to SQL Server, we found below ideas helpful: - -- Clear Docker images to create clean image from time-to-time, and clear docker cache if needed by running `docker system prune` in Command Prompt. - -- If you face `Microsoft.Data.SqlClient.SNI.dll not found` errors when debugging, try updating the below properties in the netcore\Microsoft.Data.SqlClient.csproj file and try again: - - ```xml - Unix - false - true - ``` - ## Collecting Code Coverage ### Using VSTest diff --git a/CHANGELOG.md b/CHANGELOG.md index ddcb3e38bd..04afcc4446 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,68 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +## [Preview Release 7.0.0-preview1.25257.1] - 2025-09-12 + +This update brings the following changes since the [6.1.0](release-notes/6.1/6.1.0.md) +release: + +### Breaking Changes + +- Removed `Constrained Execution Region` error handling blocks and associated + `SqlConnection` cleanup which may affect how potentially-broken connections + are expunged from the pool. + ([#3535](https://github.com/dotnet/SqlClient/pull/3535)) + +### Bug Fixes + +- Packet multiplexing disabled by default, and several bug fixes. + ([#3534](https://github.com/dotnet/SqlClient/pull/3534), + [#3537](https://github.com/dotnet/SqlClient/pull/3537)) + +### Added + +- `SqlColumnEncryptionCertificateStoreProvider` now works on Windows, Linux, + and macOS. + ([#3014](https://github.com/dotnet/SqlClient/pull/3014)) + +### Changed + +- Updated `SqlVector.Null` to return a nullable `SqlVector` instance in the + reference API to match the implementation. + ([#3521](https://github.com/dotnet/SqlClient/pull/3521)) + +- Performance improvements for all built-in + `SqlColumnEncryptionKeyStoreProvider` implementations. + ([#3554](https://github.com/dotnet/SqlClient/pull/3554)) + +- Various test improvements. + ([#3456](https://github.com/dotnet/SqlClient/pull/3456), + [#2968](https://github.com/dotnet/SqlClient/pull/2968), + [#3458](https://github.com/dotnet/SqlClient/pull/3458), + [#3494](https://github.com/dotnet/SqlClient/pull/3494), + [#3559](https://github.com/dotnet/SqlClient/pull/3559), + [#3575](https://github.com/dotnet/SqlClient/pull/3575)) + +- Codebase merge project and related cleanup. + ([#3436](https://github.com/dotnet/SqlClient/pull/3436), + [#3434](https://github.com/dotnet/SqlClient/pull/3434), + [#3448](https://github.com/dotnet/SqlClient/pull/3448), + [#3454](https://github.com/dotnet/SqlClient/pull/3454), + [#3462](https://github.com/dotnet/SqlClient/pull/3462), + [#3435](https://github.com/dotnet/SqlClient/pull/3435), + [#3492](https://github.com/dotnet/SqlClient/pull/3492), + [#3473](https://github.com/dotnet/SqlClient/pull/3473), + [#3469](https://github.com/dotnet/SqlClient/pull/3469), + [#3394](https://github.com/dotnet/SqlClient/pull/3394), + [#3493](https://github.com/dotnet/SqlClient/pull/3493), + [#3593](https://github.com/dotnet/SqlClient/pull/3593)) + +- Documentation improvements. + ([#3490](https://github.com/dotnet/SqlClient/pull/3490)) + +- Updated `Azure.Identity` dependency to v1.14.2. + ([#3538](https://github.com/dotnet/SqlClient/pull/3538)) + ## [Stable Release 6.1.1] - 2025-08-14 This update includes the following changes since the [6.1.0](6.1.0.md) release: diff --git a/NuGet.config b/NuGet.config index d93875f3fb..3e762e7fe9 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,11 +1,30 @@  + + - + + + + + + + + diff --git a/build.proj b/build.proj index 0443bbcf4e..5d8c3bc367 100644 --- a/build.proj +++ b/build.proj @@ -2,8 +2,9 @@ - - + + + @@ -29,7 +30,7 @@ true Configuration=$(Configuration);AssemblyVersion=$(SqlServerAssemblyVersion);AssemblyFileVersion=$(SqlServerAssemblyFileVersion);Version=$(SqlServerPackageVersion); Configuration=$(Configuration);AssemblyFileVersion=$(AssemblyFileVersion);TargetsWindows=$(TargetsWindows);TargetsUnix=$(TargetsUnix); - BuildProjectReferences=false;$(ProjectProperties);BuildForRelease=false;TargetNetCoreVersion=$(TargetNetCoreVersion);TargetNetFxVersion=$(TargetNetFxVersion) + $(ProjectProperties);BuildForRelease=false;TargetNetCoreVersion=$(TargetNetCoreVersion);TargetNetFxVersion=$(TargetNetFxVersion) TestResults + + @@ -84,32 +87,89 @@ - - - + + + - - + + + + + + AbstractionsPackageVersion=$(AbstractionsPackageVersion) + + + + + + + + + + + + + + + + + + + - + - + - + - + @@ -122,7 +182,10 @@ - + @@ -139,27 +202,37 @@ - + - + + - + - - + + @@ -168,7 +241,9 @@ - + @@ -177,12 +252,18 @@ - + - + @@ -191,7 +272,10 @@ - + @@ -346,13 +430,13 @@ - + - - - - - + + + + + @@ -388,7 +472,10 @@ - + @@ -398,7 +485,9 @@ - + @@ -408,7 +497,9 @@ - + diff --git a/eng/pipelines/akv-official-pipeline.yml b/eng/pipelines/akv-official-pipeline.yml index d7bc900bb8..fcaa1bb3e2 100644 --- a/eng/pipelines/akv-official-pipeline.yml +++ b/eng/pipelines/akv-official-pipeline.yml @@ -113,7 +113,7 @@ extends: sbom: enabled: ${{ parameters.runSdlTasks }} packageName: 'Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider' - packageVersion: ${{ variables.nugetPackageVersion }} + packageVersion: ${{ variables.akvPackageVersion }} tsa: # OneBranch publishes all sdl results to TSA. If TSA is disabled all SDL tools will @@ -131,8 +131,7 @@ extends: apiScanPdbPath: '${{ variables.apiScanPdbPath }}' assemblyFileVersion: '${{ variables.assemblyFileVersion }}' buildConfiguration: '${{ parameters.buildConfiguration }}' - nugetPackageVersion: '${{ variables.nugetPackageVersion }}' - mdsPackageVersion: '${{ variables.mdsPackageVersion }}' + akvPackageVersion: '${{ variables.akvPackageVersion }}' publishSymbols: '${{ parameters.publishSymbols }}' signingAppRegistrationClientId: '$(SigningAppRegistrationClientId)' signingAppRegistrationTenantId: '$(SigningAppRegistrationTenantId)' diff --git a/eng/pipelines/common/templates/jobs/build-signed-package-job.yml b/eng/pipelines/common/templates/jobs/build-signed-package-job.yml index 91eb864337..80cf59d5a7 100644 --- a/eng/pipelines/common/templates/jobs/build-signed-package-job.yml +++ b/eng/pipelines/common/templates/jobs/build-signed-package-job.yml @@ -27,8 +27,8 @@ jobs: variables: - template: ../../../libraries/variables.yml@self - ${{ if parameters.isPreview }}: - - name: NugetPackageVersion - value: $(PreviewNugetPackageVersion) + - name: mdsPackageVersion + value: $(previewMdsPackageVersion) steps: - script: SET @@ -39,7 +39,15 @@ jobs: name: GetBuildType - template: ../steps/build-all-configurations-signed-dlls-step.yml@self - + parameters: + # These values are sourced from common-variables.yml. + ${{ if eq(parameters.isPreview, false) }}: + abstractionsPackageVersion: $(abstractionsPackageVersion) + ${{ else }}: + abstractionsPackageVersion: $(abstractionsPackagePreviewVersion) + abstractionsAssemblyFileVersion: $(abstractionsAssemblyFileVersion) + mdsAssemblyFileVersion: $(mdsAssemblyFileVersion) + - template: ../steps/code-analyze-step.yml@self parameters: analyzeType: all @@ -50,7 +58,10 @@ jobs: - template: ../steps/generate-nuget-package-step.yml@self parameters: - OutputDirectory: $(artifactDirectory) + nuspecPath: $(nuspecPath) + packageVersion: $(mdsPackageVersion) + outputDirectory: $(artifactDirectory) + displayName: 'Create MDS NuGet Package' - template: ../steps/esrp-code-signing-step.yml@self parameters: @@ -64,4 +75,4 @@ jobs: - template: ../steps/publish-symbols-step.yml@self parameters: publishSymbols: ${{ parameters['PublishSymbols'] }} - symbolsArtifactName: mds_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_$(NuGetPackageVersion)_$(System.TimelineId) + symbolsArtifactName: mds_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_$(mdsPackageVersion)_$(System.TimelineId) diff --git a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml index cb3790262c..fd9755f32c 100644 --- a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml @@ -4,6 +4,13 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: + + - name: referenceType + type: string + values: + - Project + - Package + - name: poolName type: string default: $(ci_var_defaultPoolName) @@ -12,9 +19,13 @@ parameters: type: string default: ADO-MMS22-SQL19 - - name: artifactName + - name: abstractionsArtifactName + type: string + default: Abstractions.Artifact + + - name: mdsArtifactName type: string - default: Artifacts + default: MDS.Artifact - name: platform type: string @@ -28,8 +39,12 @@ parameters: type: stepList default: [] + - name: abstractionsPackageVersion + type: string + jobs: -- job: build_nugets +- job: build_mds_akv_packages_job + displayName: Build MDS & AKV Packages pool: name: ${{parameters.poolName }} @@ -44,34 +59,44 @@ jobs: - ${{ if ne(parameters.prebuildSteps, '') }}: - ${{ parameters.prebuildSteps }} # extra steps to run before the build like downloading sni and the required configuration + # If we're testing in Package mode, download the Abstractions package + # artifacts and put them in the packages/ directory in the repo root. + - ${{ if eq(parameters.referenceType, 'Package') }}: + - task: DownloadPipelineArtifact@2 + displayName: Download Abstractions Package Artifact + inputs: + artifactName: ${{ parameters.abstractionsArtifactName }} + targetPath: $(Build.SourcesDirectory)/packages + - template: ../steps/ci-project-build-step.yml@self parameters: platform: ${{ parameters.platform }} configuration: ${{ parameters.configuration }} operatingSystem: Windows build: all + abstractionsPackageVersion: ${{parameters.abstractionsPackageVersion}} - template: ../steps/generate-nuget-package-step.yml@self parameters: - NugetPackageVersion: $(NugetPackageVersion) + packageVersion: $(mdsPackageVersion) configuration: $(Configuration) nuspecPath: 'tools/specs/Microsoft.Data.SqlClient.nuspec' - OutputDirectory: $(packagePath) + outputDirectory: $(packagePath) generateSymbolsPackage: false - displayName: 'Generate NuGet package M.D.SqlClient' + displayName: 'Create MDS NuGet Package' - template: ../steps/generate-nuget-package-step.yml@self parameters: - NugetPackageVersion: $(NugetPackageVersion) + packageVersion: $(akvPackageVersion) configuration: $(Configuration) nuspecPath: 'tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec' - OutputDirectory: $(packagePath) + outputDirectory: $(packagePath) generateSymbolsPackage: false installNuget: false - displayName: 'Generate NuGet package AKV Provider' + displayName: 'Create AKV NuGet Package' - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: Artifacts' + - task: PublishPipelineArtifact@1 + displayName: 'Publish Pipeline Artifact' inputs: - PathtoPublish: $(packagePath) - ArtifactName: ${{ parameters.artifactName }} + targetPath: $(packagePath) + artifactName: ${{ parameters.mdsArtifactName }} 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 dbf5b10028..06bc31911b 100644 --- a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml @@ -4,12 +4,31 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: + - name: abstractionsArtifactName + type: string + + - name: abstractionsPackageVersion + type: string + + - name: configProperties + type: object + default: {} # - key: 'value' + + - name: configSqlFor + type: string # local, azure, or enclave + default: local + - name: debug type: boolean default: false - - name: poolName - type: string + - name: enableX64Test + type: boolean + default: true + + - name: enableX86Test + type: boolean + default: false - name: hostedPool type: boolean @@ -21,63 +40,51 @@ parameters: - name: jobDisplayName type: string - - name: usemanagedSNI - type: boolean - default: false - - - name: configProperties - type: object - default: {} # - key: 'value' - - - name: prebuildSteps - type: stepList - default: [] - - - name: artifactName + - name: mdsArtifactName type: string - default: Artifacts - - name: targetFramework + - name: mdsPackageVersion type: string - name: netcoreVersionTestUtils type: string + + - name: operatingSystem + type: string + default: '' - - name: enableX86Test - type: boolean - default: false - - - name: enableX64Test - type: boolean - default: true - - - name: testSet + - name: poolName type: string + + - name: prebuildSteps + type: stepList + default: [] - name: publishTestResults type: boolean default: false - - - name: configSqlFor - type: string # local, azure, or enclave - default: local - - - name: operatingSystem + + - name: referenceType type: string - default: '' - - - name: buildType - displayName: 'Build Type' - default: Project values: - Project - Package + - name: targetFramework + type: string + + - name: testSet + type: string + # The timeout, in minutes, for this job. - name: timeout type: string default: 90 + - name: usemanagedSNI + type: boolean + default: false + jobs: - job: ${{ format('{0}', coalesce(parameters.jobDisplayName, parameters.image, 'unknown_image')) }} @@ -98,6 +105,22 @@ jobs: value: '$(dotnetx86Path)' steps: + + # If we're testing in Package mode, download the Abstractions and MDS package + # artifacts and put them in the packages/ directory in the repo root. + - ${{ if eq(parameters.referenceType, 'Package') }}: + - task: DownloadPipelineArtifact@2 + displayName: Download Abstractions Package Artifact + inputs: + artifactName: ${{ parameters.abstractionsArtifactName }} + targetPath: $(Build.SourcesDirectory)/packages + + - task: DownloadPipelineArtifact@2 + displayName: Download MDS Package Artifact + inputs: + artifactName: ${{ parameters.mdsArtifactName }} + targetPath: $(Build.SourcesDirectory)/packages + - ${{ if ne(parameters.prebuildSteps, '') }}: - ${{ parameters.prebuildSteps }} # extra steps to run before the build like downloading sni and the required configuration @@ -227,8 +250,10 @@ jobs: - template: ../steps/build-all-tests-step.yml@self # build tests parameters: targetFramework: ${{ parameters.targetFramework }} - referenceType: ${{ parameters.buildType }} + referenceType: ${{ parameters.referenceType }} testSet: ${{ parameters.testSet }} + abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} + mdsPackageVersion: ${{ parameters.mdsPackageVersion }} ${{ if ne(parameters.operatingSystem, 'Windows') }}: OSGroup: Unix @@ -237,7 +262,7 @@ jobs: parameters: debug: ${{ parameters.debug }} targetFramework: ${{ parameters.targetFramework }} - referenceType: ${{ parameters.buildType }} + referenceType: ${{ parameters.referenceType }} testSet: ${{ parameters.testSet }} operatingSystem: ${{ parameters.operatingSystem }} @@ -279,13 +304,13 @@ jobs: parameters: debug: ${{ parameters.debug }} targetFramework: ${{ parameters.targetFramework }} - referenceType: ${{ parameters.buildType }} + referenceType: ${{ parameters.referenceType }} testSet: ${{ parameters.testSet }} msbuildArchitecture: x86 dotnetx86RootPath: $(dotnetx86RootPath) operatingSystem: ${{ parameters.operatingSystem }} - - ${{ if and(eq(parameters.publishTestResults, true), eq(parameters.buildType, 'Project')) }}: # publish test results if build type is project + - ${{ if and(eq(parameters.publishTestResults, true), eq(parameters.referenceType, 'Project')) }}: # publish test results if build type is project - template: ../steps/publish-test-results-step.yml@self parameters: debug: ${{ parameters.debug }} 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 14aea42411..c422ffdc34 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 @@ -52,8 +52,6 @@ jobs: - template: ../steps/update-nuget-config-local-feed-step.yml parameters: downloadedNugetPath: $(Pipeline.Workspace)\${{parameters.packageFolderName }} - ${{ if parameters.isPreview }}: - nugetPackageVersion: $(PreviewNugetPackageVersion) - template: ../steps/update-config-file-step.yml parameters: @@ -68,11 +66,11 @@ jobs: parameters: referenceType: Package ${{ if parameters.isPreview }}: - nugetPackageVersion: $(PreviewNugetPackageVersion) + mdsPackageVersion: $(previewMdsPackageVersion) - template: ../steps/build-and-run-tests-netcore-step.yml parameters: referenceType: Package cleanFirst: true ${{ if parameters.isPreview }}: - nugetPackageVersion: $(PreviewNugetPackageVersion) + mdsPackageVersion: $(previewMdsPackageVersion) diff --git a/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml b/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml index 009e6f2647..7595ffd49d 100644 --- a/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml +++ b/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml @@ -27,11 +27,11 @@ parameters: - name: assembly_file_version_netfx type: string - default: $(AssemblyFileVersion) + default: $(mdsAssemblyFileVersion) - name: assembly_file_version_core type: string - default: $(AssemblyFileVersion) + default: $(mdsAssemblyFileVersion) - name: isPreview type: boolean @@ -54,18 +54,18 @@ jobs: value: $(Pipeline.Workspace)\${{parameters.packageFolderName }} - name: ProductVersion #MDS product version (MDS validation) - value: $(NugetPackageVersion) + value: $(mdsPackageVersion) - name: BuildType value: $[ stageDependencies.buildMDS.build_signed_package.outputs['GetBuildType.CDP_BUILD_TYPE_COPY'] ] - ${{ if parameters.isPreview }}: - name: extractedNugetPath - value: $(extractedNugetRootPath).$(PreviewNugetPackageVersion) - - name: NugetPackageVersion - value: $(PreviewNugetPackageVersion) + value: $(extractedNugetRootPath).$(previewMdsPackageVersion) + - name: mdsPackageVersion + value: $(previewMdsPackageVersion) - name: ProductVersion - value: $(PreviewNugetPackageVersion) + value: $(previewMdsPackageVersion) steps: - script: SET @@ -75,7 +75,7 @@ jobs: displayName: 'Use NuGet' - powershell: | - #Sets Variables for AssemblyFileVersion, AssemblyVersion and NugetPackageVersion + # Sets the pipeline ASSEMBLY_VERSION variable. [Xml] $versionprops = Get-Content -Path ".\tools\props\Versions.props" Write-Host $versionprops.Project.PropertyGroup[0].AssemblyFileVersion @@ -288,55 +288,52 @@ jobs: # For NetFx we have a different FileVersion, but product versions are all # the same. Some may have extra numbering at the end. We only check for # the first parts. + $failed = 0 foreach ( $pVersion in Get-ChildItem *.dll -Path $(extractedNugetPath) -Recurse | ForEach-Object versioninfo ) { if ($pVersion.ProductVersion -Like '$(ProductVersion)*') { - Write-Host Valid Product Version:"$pVersion.ProductVersion" $pVersion.ProductVersion detected for $pVersion.FileName -ForegroundColor Green + Write-Host -ForegroundColor Green "Correct ProductVersion detected for $($pVersion.FileName): $($pVersion.ProductVersion)" } else { - Write-Host "Wrong ProductVersion detected. Expected: '$(ProductVersion)', but Detected: "$pVersion.ProductVersion"" - Exit -1 + Write-Host -ForegroundColor Red "Wrong ProductVersion detected for $($pVersion.FileName); expected: $(ProductVersion); found: $($pVersion.ProductVersion)" + $failed = 1 } - if($pVersion.FileName -like '*lib\$(CurrentNetFxVersion)*'){ - - if($pVersion.FileVersion -eq '${{parameters.assembly_file_version_netfx }}') - { - Write-Host 'Correct File version Detected for net46,' $pVersion.FileVersion -ForegroundColor Green - } - else - { - Write-Host 'Wrong File version Detected for net46,' $pVersion.FileVersion -ForegroundColor Red - Exit -1 - } + $expectedFileVersion = "${{parameters.assembly_file_version_core}}" + + if ($pVersion.FileName -like '*lib\$(CurrentNetFxVersion)*') + { + $expectedFileVersion = "${{parameters.assembly_file_version_netfx}}" + } + + if ($pVersion.FileVersion -eq $expectedFileVersion) + { + Write-Host -ForegroundColor Green "Correct FileVersion detected for $($pVersion.FileName): $($pVersion.FileVersion)" } else { - - if($pVersion.FileVersion -eq '${{parameters.assembly_file_version_core}}') - { - Write-Host 'Correct File version Detected for netcore,' $pVersion.FileVersion -ForegroundColor Green - } - else - { - Write-Host 'Wrong File version Detected for netcore and ref folder,' $pVersion.FileVersion -ForegroundColor Red - Exit -1 - } + Write-Host -ForegroundColor Red "Wrong FileVersion detected for $($pVersion.FileName); expected $($expectedFileVersion); found: $($pVersion.FileVersion)" + $failed = 1 } } + if ($failed -ne 0) + { + Exit -1 + } + Get-ChildItem *.dll -Path $(extractedNugetPath) -Recurse | ForEach-Object versioninfo - displayName: 'Verify "File Version" matches provided pipeline variable "ASSEMBLY_FILE_VERSION" for DLLs' + displayName: 'Verify "File Version" matches expected values for DLLs' - powershell: | # Change TestMicrosoftDataSqlClientVersion [Xml] $versionprops = Get-Content -Path "tools/props/Versions.props" $versionpropspath = "tools\props\Versions.props" - $versionprops.Project.PropertyGroup[$versionprops.Project.PropertyGroup.Count-1].TestMicrosoftDataSqlClientVersion ="$(NugetPackageVersion)" + $versionprops.Project.PropertyGroup[$versionprops.Project.PropertyGroup.Count-1].TestMicrosoftDataSqlClientVersion ="$(mdsPackageVersion)" Write-Host "Saving Test nuget version at $rootfolder\props ...." -ForegroundColor Green $versionprops.Save($versionpropspath) @@ -344,10 +341,16 @@ jobs: - powershell: | # Check assembly versions. + # + # GOTCHA: This expects the Versions.props file having XML elements in a + # certain order. If the order changes, this check will fail! + # + # TODO: This also isn't checking the versions of the actual assemblies in + # the package, so it isn't terribly useful. [Xml] $versionprops = Get-Content -Path "tools/props/Versions.props" - $AssemblyFileVersion = $versionprops.Project.PropertyGroup[0].AssemblyFileVersion - $AssemblyVersion = $versionprops.Project.PropertyGroup[0].AssemblyVersion + $AssemblyFileVersion = $versionprops.Project.PropertyGroup[1].AssemblyFileVersion + $AssemblyVersion = $versionprops.Project.PropertyGroup[1].AssemblyVersion if($AssemblyFileVersion -eq $AssemblyVersion) { 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 e07685407f..46318f8e06 100644 --- a/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml +++ b/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml @@ -4,31 +4,43 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: + - name: abstractionsArtifactName + type: string + + - name: abstractionsPackageVersion + type: string + - name: debug type: boolean default: false - - name: testConfigurations + - name: dependsOn type: object + default: [] - - name: dependsOn + - name: mdsArtifactName + type: string + default: MDS.Artifact + + - name: mdsPackageVersion type: string - default: '' + + - name: postTestJobs + type: jobList + default: [] - - name: buildType - displayName: 'Build Type' + - name: prebuildSteps + type: stepList + default: [] + + - name: referenceType default: Project values: - Project - Package - - name: prebuildSteps - type: stepList - default: [] - - - name: postTestJobs - type: jobList - default: [] + - name: testConfigurations + type: object # The timeout, in minutes, for each test job. - name: testsTimeout @@ -39,10 +51,7 @@ stages: - ${{ each config in parameters.testConfigurations }}: - ${{ each image in config.value.images }}: - stage: ${{ image.key }} - ${{ if ne(parameters.dependsOn, '') }}: - dependsOn: ${{ parameters.dependsOn }} - ${{ else }}: - dependsOn: [] + dependsOn: ${{ parameters.dependsOn }} jobs: - ${{ each targetFramework in config.value.TargetFrameworks }}: - ${{ each platform in config.value.buildPlatforms }}: @@ -51,13 +60,17 @@ stages: - template: ../jobs/ci-run-tests-job.yml@self parameters: debug: ${{ parameters.debug }} - buildType: ${{ parameters.buildType }} + referenceType: ${{ parameters.referenceType }} timeout: ${{ parameters.testsTimeout }} poolName: ${{ config.value.pool }} hostedPool: ${{ eq(config.value.hostedPool, true) }} image: ${{ image.value }} jobDisplayName: ${{ format('{0}_{1}_{2}', replace(targetFramework, '.', '_'), platform, testSet) }} configProperties: ${{ config.value.configProperties }} + abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} + abstractionsPackageVersion: ${{parameters.abstractionsPackageVersion}} + mdsArtifactName: ${{ parameters.mdsArtifactName }} + mdsPackageVersion: ${{ parameters.mdsPackageVersion }} prebuildSteps: ${{ parameters.prebuildSteps }} targetFramework: ${{ targetFramework }} netcoreVersionTestUtils: ${{config.value.netcoreVersionTestUtils }} @@ -77,7 +90,7 @@ stages: - template: ../jobs/ci-run-tests-job.yml@self parameters: debug: ${{ parameters.debug }} - buildType: ${{ parameters.buildType }} + referenceType: ${{ parameters.referenceType }} timeout: ${{ parameters.testsTimeout }} poolName: ${{ config.value.pool }} hostedPool: ${{ eq(config.value.hostedPool, true) }} @@ -88,6 +101,10 @@ stages: jobDisplayName: ${{ format('{0}_{1}_{2}_{3}', replace(targetFramework, '.', '_'), platform, 'NativeSNI', testSet) }} configProperties: ${{ config.value.configProperties }} useManagedSNI: ${{ useManagedSNI }} + abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} + abstractionsPackageVersion: ${{parameters.abstractionsPackageVersion}} + mdsArtifactName: ${{ parameters.mdsArtifactName }} + mdsPackageVersion: ${{ parameters.mdsPackageVersion }} prebuildSteps: ${{ parameters.prebuildSteps }} targetFramework: ${{ targetFramework }} netcoreVersionTestUtils: ${{config.value.netcoreVersionTestUtils }} diff --git a/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml b/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml index e6a6c0443a..8040b991a8 100644 --- a/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml +++ b/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml @@ -4,9 +4,15 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: - - name: AssemblyFileVersion + + - name: abstractionsPackageVersion + type: string + + - name: abstractionsAssemblyFileVersion + type: string + + - name: mdsAssemblyFileVersion type: string - default: $(AssemblyFileVersion) - name: Configuration type: string @@ -47,4 +53,4 @@ steps: inputs: solution: '**/build.proj' configuration: '${{parameters.Configuration }}' - msbuildArguments: '-p:AssemblyFileVersion=${{parameters.AssemblyFileVersion }} -t:BuildAllConfigurations -p:GenerateNuget=false -p:SigningKeyPath=$(Agent.TempDirectory)\netfxKeypair.snk' + msbuildArguments: '-p:AssemblyFileVersion=${{parameters.mdsAssemblyFileVersion }} -t:BuildAllConfigurations -p:GenerateNuget=false -p:SigningKeyPath=$(Agent.TempDirectory)\netfxKeypair.snk -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:AbstractionsAssemblyFileVersion=${{ parameters.abstractionsAssemblyFileVersion }}' diff --git a/eng/pipelines/common/templates/steps/build-all-tests-step.yml b/eng/pipelines/common/templates/steps/build-all-tests-step.yml index 826be1df8b..43ecb6aafa 100644 --- a/eng/pipelines/common/templates/steps/build-all-tests-step.yml +++ b/eng/pipelines/common/templates/steps/build-all-tests-step.yml @@ -4,31 +4,33 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: - - name: targetFramework + - name: abstractionsPackageVersion + type: string + + - name: configuration + type: string + default: '$(Configuration)' + + - name: mdsPackageVersion type: string - - name: nugetPackageVersion + - name: osGroup type: string - default: $(NugetPackageVersion) + default: '' - name: platform type: string default: $(Platform) - - - name: configuration - type: string - default: '$(Configuration)' - name: referenceType - default: Package + type: string values: - Project - Package - - - name: OSGroup - type: string - default: '' + - name: targetFramework + type: string + - name: testSet type: string @@ -40,26 +42,16 @@ steps: solution: build.proj platform: '${{parameters.platform }}' configuration: '${{parameters.configuration }}' - msbuildArguments: '-t:BuildTestsNetFx -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }}' - -# - ${{else if contains(parameters.targetFramework, 'netstandard')}}: # .NET Standard -# - task: MSBuild@1 -# displayName: 'Build Tests NetStandard' -# inputs: -# solution: build.proj -# platform: '${{parameters.platform }}' -# configuration: '${{parameters.configuration }}' -# msbuildArguments: '-t:BuildTestsNetCore -p:ReferenceType=NetStandard -p:TargetNetStandardVersion=${{parameters.targetNetStandardVersion }} -p:TF=${{parameters.targetFramework }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }}' -# condition: and(succeeded(), not(startsWith(variables['TF'], 'net4')), startsWith(variables['TargetNetStandardVersion'], 'netstandard')) + msbuildArguments: '-t:BuildTestsNetFx -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }}' -- ${{elseif eq(parameters.OSGroup, '')}}: # .NET on Windows +- ${{elseif eq(parameters.osGroup, '')}}: # .NET on Windows - task: MSBuild@1 displayName: 'Build Tests NetCore [Win]' inputs: solution: build.proj platform: '${{parameters.platform }}' configuration: '${{parameters.configuration }}' - msbuildArguments: '-t:BuildTestsNetCore -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }}' + msbuildArguments: '-t:BuildTestsNetCore -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }}' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) - ${{ else }}: # .NET on Unix @@ -69,7 +61,7 @@ steps: command: custom projects: build.proj custom: msbuild - arguments: '-t:BuildTestsNetCore -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }} -p:OSGroup=${{parameters.OSGroup }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' + arguments: '-t:BuildTestsNetCore -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:OSGroup=${{parameters.osGroup }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }}' verbosityRestore: Detailed verbosityPack: Detailed condition: and(succeeded(), ne(variables['Agent.OS'], 'Windows_NT')) diff --git a/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml b/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml index c70fe776a7..43eea4449d 100644 --- a/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml +++ b/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml @@ -18,9 +18,9 @@ parameters: - Project - Package - - name: NugetPackageVersion + - name: mdsPackageVersion type: string - default: $(NugetPackageVersion) + default: $(mdsPackageVersion) - name: platform type: string @@ -55,14 +55,14 @@ steps: inputs: solution: build.proj msbuildArchitecture: x64 - msbuildArguments: '-p:Configuration=${{parameters.configuration }} -t:BuildAKVNetCore -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }}' + msbuildArguments: '-p:Configuration=${{parameters.configuration }} -t:BuildAKVNetCore -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }}' - task: MSBuild@1 displayName: 'MSBuild Build Tests for ${{parameters.TargetNetCoreVersion }}' inputs: solution: build.proj msbuildArchitecture: x64 - msbuildArguments: '-t:BuildTestsNetCore -p:ReferenceType=${{parameters.referenceType }} -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} -p:Configuration=${{parameters.configuration }}' + msbuildArguments: '-t:BuildTestsNetCore -p:ReferenceType=${{parameters.referenceType }} -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:Configuration=${{parameters.configuration }}' # Don't run unit tests using package reference. Unit tests are only run using project reference. @@ -71,12 +71,12 @@ steps: inputs: command: test projects: 'src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.FunctionalTests.csproj' - arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} --no-build -v n --filter "category!=nonnetcoreapptests&category!=failing&category!=nonwindowstests"' + arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} --no-build -v n --filter "category!=nonnetcoreapptests&category!=failing&category!=nonwindowstests"' - task: DotNetCoreCLI@2 displayName: 'Run Manual Tests for ${{parameters.TargetNetCoreVersion }}' inputs: command: test projects: 'src\Microsoft.Data.SqlClient\tests\ManualTests\Microsoft.Data.SqlClient.ManualTesting.Tests.csproj' - arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} --no-build -v n --filter category!=nonnetcoreapptests&category!=failing&category!=nonwindowstests --collect "Code Coverage"' + arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} --no-build -v n --filter category!=nonnetcoreapptests&category!=failing&category!=nonwindowstests --collect "Code Coverage"' retryCountOnTaskFailure: ${{parameters.retryCountOnManualTests }} diff --git a/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml b/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml index 5d9f194c48..e7b35f653e 100644 --- a/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml +++ b/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml @@ -18,9 +18,9 @@ parameters: - Project - Package - - name: NugetPackageVersion + - name: mdsPackageVersion type: string - default: $(NugetPackageVersion) + default: $(mdsPackageVersion) - name: platform type: string @@ -55,13 +55,13 @@ steps: inputs: solution: build.proj msbuildArchitecture: x64 - msbuildArguments: '-p:Configuration=${{parameters.configuration }} -t:BuildAKVNetFx -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }}' + msbuildArguments: '-p:Configuration=${{parameters.configuration }} -t:BuildAKVNetFx -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }}' - task: MSBuild@1 displayName: 'MSBuild Build Tests for ${{parameters.TargetNetFxVersion }}' inputs: solution: build.proj - msbuildArguments: ' -t:BuildTestsNetFx -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:Configuration=${{parameters.configuration }} -p:Platform=${{parameters.platform }}' + msbuildArguments: ' -t:BuildTestsNetFx -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:Configuration=${{parameters.configuration }} -p:Platform=${{parameters.platform }}' # Don't run unit tests using package reference. Unit tests are only run using project reference. @@ -70,12 +70,12 @@ steps: inputs: command: test projects: 'src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.FunctionalTests.csproj' - arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" --collect "Code Coverage"' + arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" --collect "Code Coverage"' - task: DotNetCoreCLI@2 displayName: 'Run Manual Tests for ${{parameters.TargetNetFxVersion }}' inputs: command: test projects: 'src\Microsoft.Data.SqlClient\tests\ManualTests\Microsoft.Data.SqlClient.ManualTesting.Tests.csproj' - arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" --collect "Code Coverage"' + arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" --collect "Code Coverage"' retryCountOnTaskFailure: ${{parameters.retryCountOnManualTests }} diff --git a/eng/pipelines/common/templates/steps/ci-prebuild-step.yml b/eng/pipelines/common/templates/steps/ci-prebuild-step.yml index efe17856d8..4eedd5dfd4 100644 --- a/eng/pipelines/common/templates/steps/ci-prebuild-step.yml +++ b/eng/pipelines/common/templates/steps/ci-prebuild-step.yml @@ -8,13 +8,8 @@ parameters: type: boolean default: false - - name: artifactName + - name: referenceType type: string - default: Artifacts - - - name: buildType - displayName: 'Build Type' - default: Project values: - Project - Package @@ -37,21 +32,8 @@ steps: Get-ChildItem env: | Sort-Object Name displayName: 'List Environment Variables [debug]' -- ${{if eq(parameters.buildType, 'Package')}}: - - task: DownloadPipelineArtifact@2 - displayName: 'Download NuGet Package' - inputs: - buildType: current - artifact: ${{parameters.artifactName }} - patterns: '**/*.nupkg' - targetPath: $(Pipeline.Workspace)/${{parameters.artifactName }} - +- ${{if eq(parameters.referenceType, 'Package')}}: - template: update-nuget-config-local-feed-step.yml@self parameters: - downloadedNugetPath: $(Pipeline.Workspace)\${{parameters.artifactName }} + downloadedNugetPath: $(Build.SourcesDirectory)/packages debug: ${{ parameters.debug }} - -- ${{ else }}: # project - - template: ci-project-build-step.yml@self - parameters: - build: allNoDocs diff --git a/eng/pipelines/common/templates/steps/ci-project-build-step.yml b/eng/pipelines/common/templates/steps/ci-project-build-step.yml index e938c909fd..24a167ba5d 100644 --- a/eng/pipelines/common/templates/steps/ci-project-build-step.yml +++ b/eng/pipelines/common/templates/steps/ci-project-build-step.yml @@ -34,6 +34,9 @@ parameters: - all - allNoDocs + - name: abstractionsPackageVersion + type: string + steps: - template: ./ensure-dotnet-version.yml@self parameters: @@ -48,24 +51,24 @@ steps: - ${{ if or(eq(parameters.operatingSystem, 'Windows'), eq(parameters.operatingSystem, 'deferedToRuntime')) }}: - ${{ if or(eq(parameters.build, 'MDS'), eq(parameters.build, 'all'), eq(parameters.build, 'allNoDocs')) }}: - task: MSBuild@1 - displayName: 'Restore nugets [Win]' + displayName: 'Restore [Win]' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) inputs: solution: build.proj msbuildArchitecture: x64 - msbuildArguments: '-t:restore' + msbuildArguments: '-t:restore -p:AbstractionsPackageVersion=${{parameters.abstractionsPackageVersion}}' retryCountOnTaskFailure: 1 - ${{ if eq(parameters.build, 'allNoDocs') }}: - task: MSBuild@1 - displayName: 'Build Driver [Win]' + displayName: 'Build Driver (no docs) [Win]' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) inputs: solution: build.proj msbuildArchitecture: x64 platform: '${{ parameters.platform }}' configuration: '${{ parameters.configuration }}' - msbuildArguments: '-t:BuildAllConfigurations -p:GenerateDocumentationFile=false -p:GenerateNuGet=false -p:BuildNumber=${{ parameters.buildNumber }}' + msbuildArguments: '-t:BuildAllConfigurations -p:GenerateDocumentationFile=false -p:GenerateNuGet=false -p:BuildNumber=${{ parameters.buildNumber }} -p:AbstractionsPackageVersion=${{parameters.abstractionsPackageVersion}}' clean: true - ${{ if or(eq(parameters.build, 'MDS'), eq(parameters.build, 'all')) }}: @@ -77,7 +80,7 @@ steps: msbuildArchitecture: x64 platform: '${{ parameters.platform }}' configuration: '${{ parameters.configuration }}' - msbuildArguments: '-t:BuildAllConfigurations -p:GenerateNuGet=false -p:BuildNumber=${{ parameters.buildNumber }}' + msbuildArguments: '-t:BuildAllConfigurations -p:GenerateNuGet=false -p:BuildNumber=${{ parameters.buildNumber }} -p:AbstractionsPackageVersion=${{parameters.abstractionsPackageVersion}}' clean: true - ${{ if or(eq(parameters.build, 'AKV'), eq(parameters.build, 'all'), eq(parameters.build, 'allNoDocs')) }}: @@ -89,7 +92,7 @@ steps: msbuildArchitecture: x64 platform: '${{ parameters.platform }}' configuration: '${{ parameters.configuration }}' - msbuildArguments: '-t:BuildAKVNetFx -p:BuildNumber=${{ parameters.buildNumber }}' + msbuildArguments: '-t:BuildAKVNetFx -p:BuildNumber=${{ parameters.buildNumber }} -p:AbstractionsPackageVersion=${{parameters.abstractionsPackageVersion}}' - task: MSBuild@1 displayName: 'Build AKV Provider NetCore All OS [Win]' @@ -99,17 +102,17 @@ steps: msbuildArchitecture: x64 platform: '${{ parameters.platform }}' configuration: '${{ parameters.configuration }}' - msbuildArguments: '-t:BuildAKVNetCoreAllOS -p:BuildNumber=${{ parameters.buildNumber }}' + msbuildArguments: '-t:BuildAKVNetCoreAllOS -p:BuildNumber=${{ parameters.buildNumber }} -p:AbstractionsPackageVersion=${{parameters.abstractionsPackageVersion}}' - ${{ if or(eq(parameters.operatingSystem, 'Linux'), eq(parameters.operatingSystem, 'MacOS'), eq(parameters.operatingSystem, 'deferedToRuntime')) }}: - task: DotNetCoreCLI@2 - displayName: 'Build Driver [non-Win]' + displayName: 'Build Driver [${{ parameters.operatingSystem }}]' condition: and(succeeded(), ne(variables['Agent.OS'], 'Windows_NT')) inputs: command: custom projects: build.proj custom: msbuild - arguments: '-t:BuildAll -p:TestEnabled=true -p:GenerateDocumentationFile=false -p:configuration=${{ parameters.configuration }}' + arguments: '-t:BuildAll -p:TestEnabled=true -p:GenerateDocumentationFile=false -p:configuration=${{ parameters.configuration }} -p:AbstractionsPackageVersion=${{parameters.abstractionsPackageVersion}}' verbosityRestore: Detailed verbosityPack: Detailed retryCountOnTaskFailure: 1 diff --git a/eng/pipelines/common/templates/steps/copy-dlls-for-test-step.yml b/eng/pipelines/common/templates/steps/copy-dlls-for-test-step.yml index 098286866c..3b18dcf5bc 100644 --- a/eng/pipelines/common/templates/steps/copy-dlls-for-test-step.yml +++ b/eng/pipelines/common/templates/steps/copy-dlls-for-test-step.yml @@ -83,6 +83,6 @@ steps: $software = '${{parameters.softwareFolder}}' $symbols = '${{parameters.symbolsFolder}}' - Get-ChildItem -recurse "$software\*.dll" - Get-ChildItem -recurse "$symbols\*.pdb" + Get-ChildItem -recurse "$software\*.dll" | ForEach-Object VersionInfo + Get-ChildItem -recurse "$symbols\*.pdb" | ForEach-Object VersionInfo displayName: 'List the prepared files' diff --git a/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml b/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml index 4e32f989c3..a9ce1f2d83 100644 --- a/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml +++ b/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml @@ -6,17 +6,15 @@ parameters: - name: nuspecPath type: string - default: '$(nuspecPath)' - - name: NugetPackageVersion + - name: packageVersion type: string - default: '$(NugetPackageVersion)' - - name: OutputDirectory + - name: outputDirectory type: string default: '$(Build.SourcesDirectory)/packages' - - name: Configuration + - name: configuration type: string default: '$(Configuration)' @@ -26,7 +24,6 @@ parameters: - name: displayName type: string - default: 'NuGet pack with snupkg' - name: installNuget type: boolean @@ -55,6 +52,6 @@ steps: inputs: command: custom ${{ if parameters.generateSymbolsPackage }}: - arguments: 'pack -Symbols -SymbolPackageFormat snupkg ${{parameters.nuspecPath}} -Version ${{parameters.NugetPackageVersion}} -OutputDirectory ${{parameters.OutputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.Configuration}};ReferenceType=${{parameters.referenceType}}"' + arguments: 'pack -Symbols -SymbolPackageFormat snupkg ${{parameters.nuspecPath}} -Version ${{parameters.packageVersion}} -OutputDirectory ${{parameters.outputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.Configuration}};ReferenceType=${{parameters.referenceType}}"' ${{else }}: - arguments: 'pack ${{parameters.nuspecPath}} -Version ${{parameters.NugetPackageVersion}} -OutputDirectory ${{parameters.OutputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.Configuration}};ReferenceType=${{parameters.referenceType}}"' + arguments: 'pack ${{parameters.nuspecPath}} -Version ${{parameters.packageVersion}} -OutputDirectory ${{parameters.outputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.Configuration}};ReferenceType=${{parameters.referenceType}}"' diff --git a/eng/pipelines/common/templates/steps/publish-symbols-step.yml b/eng/pipelines/common/templates/steps/publish-symbols-step.yml index 5f8d2e6a7d..8999a6cc1d 100644 --- a/eng/pipelines/common/templates/steps/publish-symbols-step.yml +++ b/eng/pipelines/common/templates/steps/publish-symbols-step.yml @@ -15,7 +15,7 @@ parameters: - name: symbolsVersion type: string - default: '$(NuGetPackageVersion)' + default: '$(mdsPackageVersion)' - name: symbolServer type: string diff --git a/eng/pipelines/common/templates/steps/run-all-tests-step.yml b/eng/pipelines/common/templates/steps/run-all-tests-step.yml index b9a870ada4..27da9e4e73 100644 --- a/eng/pipelines/common/templates/steps/run-all-tests-step.yml +++ b/eng/pipelines/common/templates/steps/run-all-tests-step.yml @@ -11,9 +11,9 @@ parameters: - name: targetFramework type: string - - name: nugetPackageVersion + - name: mdsPackageVersion type: string - default: $(NugetPackageVersion) + default: $(mdsPackageVersion) - name: platform type: string @@ -62,9 +62,9 @@ steps: platform: '${{parameters.platform }}' configuration: '${{parameters.configuration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: - msbuildArguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }}' + msbuildArguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }}' ${{ else }}: # x86 - msbuildArguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' + msbuildArguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) retryCountOnTaskFailure: 1 @@ -76,9 +76,9 @@ steps: platform: '${{parameters.platform }}' configuration: '${{parameters.configuration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: - msbuildArguments: '-t:RunFunctionalTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }}' + msbuildArguments: '-t:RunFunctionalTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }}' ${{ else }}: # x86 - msbuildArguments: '-t:RunFunctionalTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' + msbuildArguments: '-t:RunFunctionalTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) retryCountOnTaskFailure: 1 @@ -90,9 +90,9 @@ steps: platform: '${{parameters.platform }}' configuration: '${{parameters.configuration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: - msbuildArguments: '-t:RunManualTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }}' + msbuildArguments: '-t:RunManualTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }}' ${{ else }}: # x86 - msbuildArguments: '-t:RunManualTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' + msbuildArguments: '-t:RunManualTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' condition: eq(variables['Agent.OS'], 'Windows_NT') retryCountOnTaskFailure: 2 @@ -104,7 +104,7 @@ steps: command: custom projects: build.proj custom: msbuild - arguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' + arguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' verbosityRestore: Detailed verbosityPack: Detailed retryCountOnTaskFailure: 1 @@ -116,7 +116,7 @@ steps: command: custom projects: build.proj custom: msbuild - arguments: '-t:RunFunctionalTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' + arguments: '-t:RunFunctionalTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' verbosityRestore: Detailed verbosityPack: Detailed retryCountOnTaskFailure: 1 @@ -128,7 +128,7 @@ steps: command: custom projects: build.proj custom: msbuild - arguments: '-t:RunManualTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' + arguments: '-t:RunManualTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' verbosityRestore: Detailed verbosityPack: Detailed retryCountOnTaskFailure: 2 diff --git a/eng/pipelines/common/templates/steps/update-nuget-config-local-feed-step.yml b/eng/pipelines/common/templates/steps/update-nuget-config-local-feed-step.yml index 4eac341108..f7bf252ebe 100644 --- a/eng/pipelines/common/templates/steps/update-nuget-config-local-feed-step.yml +++ b/eng/pipelines/common/templates/steps/update-nuget-config-local-feed-step.yml @@ -11,10 +11,6 @@ parameters: - name: downloadedNugetPath # path to the downloaded nuget files type: string - - name: nugetPackageVersion - type: string - default: $(NugetPackageVersion) - steps: - powershell: | # Get a list of package sources available @@ -34,7 +30,7 @@ steps: [Xml] $nugetConfig = Get-Content -Path "NuGet.config" $Value = Resolve-Path ${{parameters.downloadedNugetPath }} $newAdd = $nugetConfig.CreateElement("add") - $newAdd.SetAttribute("key","Package source") + $newAdd.SetAttribute("key","pipeline") $newAdd.SetAttribute("value", "$Value/" ) $nugetConfig.configuration.packageSources.AppendChild($newAdd) $nugetConfig.Save("$rootFolder/NuGet.config") @@ -45,39 +41,3 @@ steps: # Display the content of the NuGet.config file Get-Content -Path "NuGet.config" displayName: 'Read NuGet.config [debug]' - -- task: DotNetCoreCLI@2 - displayName: 'Restore NuGets' - inputs: - command: 'custom' - custom: 'msbuild' - arguments: 'build.proj -t:restore' - feedsToUse: 'select' - -- powershell: | - $Doc = [xml](Get-Content "./Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj") - $parent_xpath = '/Project/ItemGroup/ProjectReference' - $node = $Doc.SelectSingleNode($parent_xpath) - $parentNode = $node.ParentNode - while($node -ne $null) { - $node.ParentNode.RemoveChild($node) - $node = $Doc.SelectSingleNode($parent_xpath) - } - - $parent_xpath = '/Project/ItemGroup/PackageReference[@Include="Microsoft.Data.SqlClient"]' - $node = $Doc.SelectSingleNode($parent_xpath) - - if($node -eq $null){ - $packagerefnode = $doc.createelement("packagereference") - $value = $doc.selectsinglenode('/project/itemgroup/projectreference') - $attrinclude = $doc.createattribute("include") - $attrinclude.value = "microsoft.data.sqlclient" - $packagerefnode.attributes.append($attrinclude) - $parentNode.AppendChild($packageRefNode) - } - - $currentFolder = Get-Location - $filePath = Join-Path $currentFolder "Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj" - $Doc.Save($filePath) - workingDirectory: 'src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider' - displayName: 'Update AKV Project Ref to Package Ref (.NET Framework/Core)' diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index 353122828b..df7e1993f1 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -58,9 +58,12 @@ parameters: type: object default: [net462, net8.0] -- name: buildType - displayName: 'Build Type' - default: Project +# The way we will reference sibling projects in the .csproj files: +# Project - use references. +# Package - use references to NuGet packages in the +# packages/ directory. +- name: referenceType + displayName: 'Reference Type' values: - Project - Package @@ -89,20 +92,47 @@ parameters: variables: - template: libraries/ci-build-variables.yml@self - - name: artifactName - value: Artifacts + - name: abstractionsArtifactName + value: Abstractions.Artifact + + - name: mdsArtifactName + value: MDS.Artifact - name: defaultHostedPoolName value: 'Azure Pipelines' stages: - - stage: build_nugets - displayName: 'Build NuGet Packages' + + # Build the Abstractions package, and publish it to the pipeline artifacts + # under the given artifact name. + - template: stages/build-abstractions-package-ci-stage.yml@self + parameters: + buildConfiguration: Release + abstractionsPackageVersion: $(abstractionsPackageVersion) + artifactName: $(abstractionsArtifactName) + ${{if eq(parameters.debug, 'true')}}: + verbosity: diagnostic + + # Build MDS and its NuGet packages. + - stage: build_mds_akv_packages_stage + displayName: 'Build MDS & AKV Packages' + + # When building MDS via packages, we must depend on the Abstractions + # package. + ${{ if eq(parameters.referenceType, 'Package') }}: + dependsOn: + - build_abstractions_package_stage + ${{ else }}: + dependsOn: [] + jobs: - template: common/templates/jobs/ci-build-nugets-job.yml@self parameters: + referenceType: ${{ parameters.referenceType }} configuration: ${{ parameters.buildConfiguration }} - artifactName: $(artifactName) + abstractionsPackageVersion: $(abstractionsPackageVersion) + abstractionsArtifactName: $(abstractionsArtifactName) + mdsArtifactName: $(mdsArtifactName) ${{if ne(parameters.SNIVersion, '')}}: prebuildSteps: - template: common/templates/steps/override-sni-version.yml@self @@ -110,44 +140,48 @@ stages: SNIVersion: ${{parameters.SNIVersion}} SNIValidationFeed: ${{parameters.SNIValidationFeed}} + # Run the stress tests, if desired. - ${{ if eq(parameters.enableStressTests, true) }}: - template: stages/stress-tests-ci-stage.yml@self parameters: buildConfiguration: ${{ parameters.buildConfiguration }} - dependsOn: [build_nugets] + dependsOn: [build_mds_akv_packages_stage] pipelineArtifactName: $(artifactName) - mdsPackageVersion: $(NugetPackageVersion) + mdsPackageVersion: $(mdsPackageVersion) ${{ if eq(parameters.debug, 'true') }}: verbosity: 'detailed' - + + # Run the MDS and AKV tests. - template: common/templates/stages/ci-run-tests-stage.yml@self parameters: debug: ${{ parameters.debug }} - buildType: ${{ parameters.buildType }} + referenceType: ${{ parameters.referenceType }} testsTimeout: ${{ parameters.testsTimeout }} - ${{ if eq(parameters.buildType, 'Package') }}: - dependsOn: build_nugets - - ${{if ne(parameters.SNIVersion, '')}}: - prebuildSteps: # steps to run prior to building and running tests on each job + abstractionsArtifactName: $(abstractionsArtifactName) + abstractionsPackageVersion: $(abstractionsPackageVersion) + mdsArtifactName: $(mdsArtifactName) + mdsPackageVersion: $(mdsPackageVersion) + + # When testing MDS via packages, we must depend on the Abstractions and + # MDS packages. + ${{ if eq(parameters.referenceType, 'Package') }}: + dependsOn: + - build_abstractions_package_stage + - build_mds_akv_packages_stage + + prebuildSteps: # steps to run prior to building and running tests on each job + - ${{if ne(parameters.SNIVersion, '')}}: - template: common/templates/steps/override-sni-version.yml@self parameters: SNIVersion: ${{parameters.SNIVersion}} SNIValidationFeed: ${{parameters.SNIValidationFeed}} - - template: common/templates/steps/ci-prebuild-step.yml@self - parameters: - debug: ${{ parameters.debug }} - artifactName: $(artifactName) - buildType: ${{ parameters.buildType }} - ${{else}}: - prebuildSteps: # steps to run prior to building and running tests on each job - - template: common/templates/steps/ci-prebuild-step.yml@self - parameters: - debug: ${{ parameters.debug }} - artifactName: $(artifactName) - buildType: ${{ parameters.buildType }} - ${{ if eq(parameters.buildType, 'Project') }}: # only run the code coverage job if the build type is project + - template: common/templates/steps/ci-prebuild-step.yml@self + parameters: + debug: ${{ parameters.debug }} + referenceType: ${{ parameters.referenceType }} + + ${{ if eq(parameters.referenceType, 'Project') }}: # only run the code coverage job if the build type is project postTestJobs: # jobs to run after the tests are done - template: common/templates/jobs/ci-code-coverage-job.yml@self parameters: @@ -354,9 +388,8 @@ stages: # ways to detect if they were present) won't run consistently across forks and non-forks. ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: AADPasswordConnectionString: $(AAD_PASSWORD_CONN_STR) - AADServicePrincipalId: $(AADServicePrincipalId) - ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: AADServicePrincipalSecret: $(AADServicePrincipalSecret) + AADServicePrincipalId: $(AADServicePrincipalId) AzureKeyVaultUrl: $(AzureKeyVaultUrl) AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) SupportsIntegratedSecurity: false @@ -383,9 +416,8 @@ stages: AADAuthorityURL: $(AADAuthorityURL) ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: AADPasswordConnectionString: $(AAD_PASSWORD_CONN_STR_eastus) - AADServicePrincipalId: $(AADServicePrincipalId) - ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: AADServicePrincipalSecret: $(AADServicePrincipalSecret) + AADServicePrincipalId: $(AADServicePrincipalId) AzureKeyVaultUrl: $(AzureKeyVaultUrl) AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) SupportsIntegratedSecurity: false @@ -393,37 +425,6 @@ stages: LocalDbAppName: $(LocalDbAppName) LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) - ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: # only run enclave jobs if the password is available - windows_enclave_sql: - pool: ADO-CI-AE-1ES-Pool - images: - Win22_Enclave_Sql19: ADO-MMS22-SQL19 - TargetFrameworks: ${{parameters.targetFrameworks }} - netcoreVersionTestUtils: ${{parameters.netcoreVersionTestUtils }} - buildPlatforms: ${{parameters.buildPlatforms }} - testSets: [AE] - useManagedSNI: ${{parameters.useManagedSNI }} - codeCovTargetFrameworks: ${{parameters.codeCovTargetFrameworks }} - configSqlFor: enclave - operatingSystem: Windows - configProperties: - # config.json properties - TCPConnectionStringHGSVBS: $(SQL_TCP_CONN_STRING_HGSVBS) - TCPConnectionStringNoneVBS: $(SQL_TCP_CONN_STRING_NoneVBS) - TCPConnectionStringAASSGX: $(SQL_TCP_CONN_STRING_AASSGX) - EnclaveEnabled: true - AADAuthorityURL: $(AADAuthorityURL) - AADServicePrincipalId: $(AADServicePrincipalId) - ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: - AADServicePrincipalSecret: $(AADServicePrincipalSecret) - AzureKeyVaultUrl: $(AzureKeyVaultUrl) - AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) - SupportsIntegratedSecurity: $(SupportsIntegratedSecurity) - UserManagedIdentityClientId: $(UserManagedIdentityClientId) - AliasName: $(SQLAliasName) - LocalDbAppName: $(LocalDbAppName) - LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) - # self hosted SQL Server on Linux linux_sql_19_22: pool: ${{parameters.defaultPoolName }} @@ -469,9 +470,8 @@ stages: AADAuthorityURL: $(AADAuthorityURL) ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: AADPasswordConnectionString: $(AAD_PASSWORD_CONN_STR) - AADServicePrincipalId: $(AADServicePrincipalId) - ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: AADServicePrincipalSecret: $(AADServicePrincipalSecret) + AADServicePrincipalId: $(AADServicePrincipalId) AzureKeyVaultUrl: $(AzureKeyVaultUrl) AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) SupportsIntegratedSecurity: false @@ -479,7 +479,65 @@ stages: LocalDbAppName: $(LocalDbAppName) LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) - ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: # only run enclave jobs if the password is available + # Self hosted SQL Server on Mac + mac_sql_22: + pool: $(defaultHostedPoolName) + hostedPool: true + images: + MacOSLatest_Sql22: macos-latest + TargetFrameworks: ${{parameters.targetFrameworksLinux }} + netcoreVersionTestUtils: ${{parameters.netcoreVersionTestUtils }} + buildPlatforms: [AnyCPU] + testSets: ${{parameters.testSets }} + useManagedSNI: [true] + codeCovTargetFrameworks: ${{parameters.codeCovTargetFrameworks }} + configSqlFor: local + operatingSystem: Mac + configProperties: + # config.json properties + TCPConnectionString: $(SQL_TCP_CONN_STRING) + NPConnectionString: $(SQL_NP_CONN_STRING) + SupportsIntegratedSecurity: false + ManagedIdentitySupported: false + LocalDbAppName: $(LocalDbAppName) + LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) + + # Enclave tests + # + # Only run enclave jobs if the password is available, which it won't be + # for forked PRs. + # + ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: + windows_enclave_sql: + pool: ADO-CI-AE-1ES-Pool + images: + Win22_Enclave_Sql19: ADO-MMS22-SQL19 + TargetFrameworks: ${{parameters.targetFrameworks }} + netcoreVersionTestUtils: ${{parameters.netcoreVersionTestUtils }} + buildPlatforms: ${{parameters.buildPlatforms }} + testSets: [AE] + useManagedSNI: ${{parameters.useManagedSNI }} + codeCovTargetFrameworks: ${{parameters.codeCovTargetFrameworks }} + configSqlFor: enclave + operatingSystem: Windows + configProperties: + # config.json properties + TCPConnectionStringHGSVBS: $(SQL_TCP_CONN_STRING_HGSVBS) + TCPConnectionStringNoneVBS: $(SQL_TCP_CONN_STRING_NoneVBS) + TCPConnectionStringAASSGX: $(SQL_TCP_CONN_STRING_AASSGX) + EnclaveEnabled: true + AADAuthorityURL: $(AADAuthorityURL) + AADServicePrincipalId: $(AADServicePrincipalId) + ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: + AADServicePrincipalSecret: $(AADServicePrincipalSecret) + AzureKeyVaultUrl: $(AzureKeyVaultUrl) + AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) + SupportsIntegratedSecurity: $(SupportsIntegratedSecurity) + UserManagedIdentityClientId: $(UserManagedIdentityClientId) + AliasName: $(SQLAliasName) + LocalDbAppName: $(LocalDbAppName) + LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) + linux_enclave_sql: pool: ADO-CI-AE-1ES-Pool images: @@ -507,26 +565,3 @@ stages: UserManagedIdentityClientId: $(UserManagedIdentityClientId) LocalDbAppName: $(LocalDbAppName) LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) - - # Self hosted SQL Server on Mac - mac_sql_22: - pool: $(defaultHostedPoolName) - hostedPool: true - images: - MacOSLatest_Sql22: macos-latest - TargetFrameworks: ${{parameters.targetFrameworksLinux }} - netcoreVersionTestUtils: ${{parameters.netcoreVersionTestUtils }} - buildPlatforms: [AnyCPU] - testSets: ${{parameters.testSets }} - useManagedSNI: [true] - codeCovTargetFrameworks: ${{parameters.codeCovTargetFrameworks }} - configSqlFor: local - operatingSystem: Mac - configProperties: - # config.json properties - TCPConnectionString: $(SQL_TCP_CONN_STRING) - NPConnectionString: $(SQL_NP_CONN_STRING) - SupportsIntegratedSecurity: false - ManagedIdentitySupported: false - LocalDbAppName: $(LocalDbAppName) - LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) diff --git a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml index 336bd97ab5..6eafba83cf 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml @@ -74,13 +74,6 @@ parameters: # parameters are shown up in ADO UI in a build queue time type: object default: [net462, net8.0] -- name: buildType - displayName: 'Build Type' - default: Package - values: - - Project - - Package - - name: buildConfiguration displayName: 'Build Configuration' default: Release @@ -109,7 +102,7 @@ extends: testSets: ${{ parameters.testSets }} useManagedSNI: ${{ parameters.useManagedSNI }} codeCovTargetFrameworks: ${{ parameters.codeCovTargetFrameworks }} - buildType: ${{ parameters.buildType }} + referenceType: Package 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 38325d38ca..6169893061 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml @@ -66,13 +66,6 @@ parameters: # parameters are shown up in ADO UI in a build queue time type: object default: [net462, net8.0] -- name: buildType - displayName: 'Build Type' - default: Project - values: - - Project - - Package - - name: buildConfiguration displayName: 'Build Configuration' default: Release @@ -101,7 +94,7 @@ extends: testSets: ${{ parameters.testSets }} useManagedSNI: ${{ parameters.useManagedSNI }} codeCovTargetFrameworks: ${{ parameters.codeCovTargetFrameworks }} - buildType: ${{ parameters.buildType }} + referenceType: Project 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 b494a87426..ddf3ebd080 100644 --- a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml @@ -107,14 +107,14 @@ extends: softwareFolder: $(softwareFolder) symbolsFolder: $(symbolsFolder) softwarename: Microsoft.Data.SqlClient - versionNumber: $(AssemblyFileVersion) + versionNumber: $(mdsAssemblyFileVersion) codeql: compiled: enabled: ${{ not(parameters['isPreview']) }} sbom: enabled: ${{ not(parameters['isPreview']) }} packageName: Microsoft.Data.SqlClient - packageVersion: $(NugetPackageVersion) + packageVersion: $(mdsPackageVersion) policheck: enabled: ${{ not(parameters['isPreview']) }} break: true # always break the build on policheck issues. You can disable it by setting to 'false' diff --git a/eng/pipelines/jobs/build-akv-official-job.yml b/eng/pipelines/jobs/build-akv-official-job.yml index a4374b773b..cbed1e2cbc 100644 --- a/eng/pipelines/jobs/build-akv-official-job.yml +++ b/eng/pipelines/jobs/build-akv-official-job.yml @@ -5,6 +5,9 @@ ################################################################################# parameters: + - name: akvPackageVersion + type: string + - name: apiScanDllPath type: string @@ -17,12 +20,6 @@ parameters: - name: buildConfiguration type: string - - name: nugetPackageVersion - type: string - - - name: mdsPackageVersion - type: string - - name: publishSymbols type: boolean @@ -90,7 +87,7 @@ jobs: parameters: assemblyFileVersion: '${{ parameters.assemblyFileVersion }}' buildConfiguration: '${{ parameters.buildConfiguration }}' - mdsPackageVersion: '${{ parameters.mdsPackageVersion }}' + akvPackageVersion: '${{ parameters.akvPackageVersion }}' - ${{ each targetFramework in parameters.targetFrameworks }}: - template: ../steps/compound-extract-akv-apiscan-files-step.yml @@ -104,7 +101,7 @@ jobs: - template: ../steps/roslyn-analyzers-akv-step.yml@self parameters: buildConfiguration: '${{ parameters.buildConfiguration }}' - mdsPackageVersion: '${{ parameters.mdsPackageVersion }}' + akvPackageVersion: '${{ parameters.akvPackageVersion }}' - template: ../steps/compound-esrp-code-signing-step.yml@self parameters: @@ -120,7 +117,7 @@ jobs: parameters: buildConfiguration: '${{ parameters.buildConfiguration }}' generateSymbolsPackage: true # Always generate symbols, even if they are not published - packageVersion: '${{ parameters.nugetPackageVersion }}' + packageVersion: '${{ parameters.akvPackageVersion }}' nuspecPath: '$(REPO_ROOT)/tools/specs/add-ons/$(PACKAGE_NAME).nuspec' outputDirectory: '$(ARTIFACT_PATH)' referenceType: 'Package' @@ -138,7 +135,7 @@ jobs: - ${{ if parameters.publishSymbols }}: - template: ../steps/compound-publish-symbols-step.yml@self parameters: - artifactName: 'akv_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_${{ parameters.nugetPackageVersion }}_$(System.TimelineId)' + artifactName: 'akv_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_${{ parameters.akvPackageVersion }}_$(System.TimelineId)' azureSubscription: '${{ parameters.symbolsAzureSubscription }}' publishProjectName: '${{ parameters.symbolsPublishProjectName }}' packageName: '$(PACKAGE_NAME)' @@ -151,4 +148,4 @@ jobs: Windows_NT/${{ parameters.buildConfiguration }}.AnyCPU/AzureKeyVaultProvider/**/$(PACKAGE_NAME).pdb AnyOS/${{ parameters.buildConfiguration }}.AnyCPU/AzureKeyVaultProvider/**/$(PACKAGE_NAME).pdb uploadAccount: '${{ parameters.symbolsUploadAccount }}' - version: '${{ parameters.nugetPackageVersion }}' + version: '${{ parameters.akvPackageVersion }}' diff --git a/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml b/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml new file mode 100644 index 0000000000..13bf3618fb --- /dev/null +++ b/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml @@ -0,0 +1,148 @@ +################################################################################ +# 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 job packs the Abstractions package into NuGet and symbols packages and +# publishes them as a named pipeline artifact. +# +# This template defines a job named 'pack_abstractions_package_job' that can be +# depended on by downstream jobs. + +parameters: + + # The version to apply to the Abstractions NuGet package and its assemblies. + - name: abstractionsPackageVersion + type: string + + # The name to apply to the published pipeline artifact. + - name: artifactName + type: string + default: Abstractions.Artifact + + # The type of build to test (Release or Debug) + - name: buildConfiguration + type: string + values: + - Release + - Debug + + # The list of upstream jobs to depend on. + - name: dependsOn + type: object + default: [] + + # The verbosity level for the dotnet CLI commands. + - name: verbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + +jobs: + + - job: pack_abstractions_package_job + displayName: 'Pack Abstractions Package' + + dependsOn: ${{ parameters.dependsOn }} + + pool: + name: Azure Pipelines + vmImage: ubuntu-latest + + variables: + + # The Abstractions project file to use for all dotnet CLI commands. + - name: project + value: src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj + + # The directory where the NuGet packages will be staged before being + # published as pipeline artifacts. + - name: dotnetPackagesDir + value: $(Build.StagingDirectory)/dotnetPackages + + # dotnet CLI arguments common to all commands. + - name: commonArguments + value: >- + --verbosity ${{ parameters.verbosity }} + + # dotnet CLI arguments for build/test/pack commands + - name: buildArguments + value: >- + $(commonArguments) + --configuration ${{ parameters.buildConfiguration }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} + + # 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 + + # 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: $(project) + arguments: $(commonArguments) + + # Build the solution. + - task: DotNetCoreCLI@2 + displayName: Build Solution + inputs: + command: custom + custom: build + projects: $(project) + arguments: $(buildArguments) --no-restore + + # Create the NuGet packages. + - task: DotNetCoreCLI@2 + displayName: Create NuGet Package + inputs: + command: custom + custom: pack + projects: $(project) + arguments: $(buildArguments) --no-build -o $(dotnetPackagesDir) + + # Publish the NuGet packages as a named pipeline artifact. + - task: PublishPipelineArtifact@1 + displayName: Publish Pipeline Artifact + inputs: + targetPath: $(dotnetPackagesDir) + artifactName: ${{ parameters.artifactName }} + publishLocation: pipeline diff --git a/eng/pipelines/jobs/stress-tests-ci-job.yml b/eng/pipelines/jobs/stress-tests-ci-job.yml index 2e01470fe5..cc2913c367 100644 --- a/eng/pipelines/jobs/stress-tests-ci-job.yml +++ b/eng/pipelines/jobs/stress-tests-ci-job.yml @@ -4,7 +4,7 @@ # file in the project root for more information. ################################################################################ -# This stage builds and runs stress tests against an MDS NuGet package available +# This job builds and runs stress tests against an MDS NuGet package available # as a pipeline artifact. # # The stress tests are located here: diff --git a/eng/pipelines/jobs/test-abstractions-package-ci-job.yml b/eng/pipelines/jobs/test-abstractions-package-ci-job.yml new file mode 100644 index 0000000000..a5e0f98978 --- /dev/null +++ b/eng/pipelines/jobs/test-abstractions-package-ci-job.yml @@ -0,0 +1,174 @@ +################################################################################ +# 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 job builds the Abstractions package and runs its tests for a set of .NET +# runtimes. +# +# This template defines a job named 'test_abstractions_package_job_' +# that can be depended on by downstream jobs. + +parameters: + + # The type of build to test (Release or Debug) + - name: buildConfiguration + type: string + values: + - Release + - Debug + + # The prefix to prepend to the job's display name: + # + # [] Run Stress Tests + # + - name: displayNamePrefix + type: string + + # The suffix to append to the job name. + - name: jobNameSuffix + type: string + + # The list of .NET Framework runtimes to test against. + - name: netFrameworkRuntimes + type: object + default: [] + + # The list of .NET runtimes to test against. + - name: netRuntimes + type: object + default: [] + + # The name of the Azure Pipelines pool to use. + - name: poolName + type: string + + # The verbosity level for the dotnet CLI commands. + - name: verbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + + # The pool VM image to use. + - name: vmImage + type: string + +jobs: + + - job: test_abstractions_package_job_${{ parameters.jobNameSuffix }} + displayName: '[${{ parameters.displayNamePrefix }}] Test Abstractions Package' + pool: + name: ${{ parameters.poolName }} + + # Images provided by Azure Pipelines must be selected using 'vmImage'. + ${{ if eq(parameters.poolName, 'Azure Pipelines') }}: + vmImage: ${{ parameters.vmImage }} + # Images provided by 1ES must be selected using a demand. + ${{ else }}: + demands: + - imageOverride -equals ${{ parameters.vmImage }} + + variables: + + # The Abstractions test project file to use for all dotnet CLI commands. + - name: project + value: src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj + + # dotnet CLI arguments common to all commands. + - name: commonArguments + value: >- + --verbosity ${{ parameters.verbosity }} + + # dotnet CLI arguments for build/test/pack commands + - name: buildArguments + value: >- + $(commonArguments) + --configuration ${{ parameters.buildConfiguration }} + + # 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 + + # The Windows agent images include a suitable .NET Framework runtime, so + # we don't have to install one explicitly. + + # 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: $(project) + arguments: $(commonArguments) + + # Build the solution. + - task: DotNetCoreCLI@2 + displayName: Build Solution + inputs: + command: custom + custom: build + projects: $(project) + arguments: $(buildArguments) --no-restore + + # Run the tests for each .NET runtime. + - ${{ each runtime in parameters.netRuntimes }}: + - task: DotNetCoreCLI@2 + displayName: Test [${{ runtime }}] + inputs: + command: custom + custom: test + projects: $(project) + arguments: $(buildArguments) --no-build -f ${{ runtime }} + + # Run the tests for each .NET Framework runtime. + - ${{ each runtime in parameters.netFrameworkRuntimes }}: + - task: DotNetCoreCLI@2 + displayName: Test [${{ runtime }}] + inputs: + command: custom + custom: test + projects: $(project) + arguments: $(buildArguments) --no-build -f ${{ runtime }} diff --git a/eng/pipelines/libraries/ci-build-variables.yml b/eng/pipelines/libraries/ci-build-variables.yml index 122281e083..6b6ac2a825 100644 --- a/eng/pipelines/libraries/ci-build-variables.yml +++ b/eng/pipelines/libraries/ci-build-variables.yml @@ -6,7 +6,6 @@ variables: - group: 'ADO Build properties' - - group: 'ADO CI Packaging' - group: 'ADO Test Configuration Properties' - name: buildNumber @@ -15,8 +14,12 @@ variables: value: 'net8.0' - name: SQLTarget value: 'localhost' - - name: NugetPackageVersion - value: $(Major).$(Minor)$(Patch)-pull.1$(buildnumber) + - name: abstractionsPackageVersion + value: 1.0.0.$(buildNumber)-ci + - name: akvPackageVersion + value: 7.0.0.$(buildNumber)-ci + - name: mdsPackageVersion + value: 7.0.0.$(buildNumber)-ci - name: skipComponentGovernanceDetection value: true - name: runCodesignValidationInjection diff --git a/eng/pipelines/libraries/common-variables.yml b/eng/pipelines/libraries/common-variables.yml index 8fc6aec755..023e59013e 100644 --- a/eng/pipelines/libraries/common-variables.yml +++ b/eng/pipelines/libraries/common-variables.yml @@ -26,6 +26,36 @@ variables: - name: artifactDirectory value: '$(REPOROOT)/packages' + # C# assembly versions must be in the format: Major.Minor.Build.Revision, but + # $(Build.BuildNumber) has the format XXX.YY. Additionally, each version part + # must be a positive 16-bit integer less than 65535. Simply concatenating + # both parts of $(Build.BuildNumber) could produce values larger than 65534, + # so we must omit the second part entirely. Unfortunately, this may result + # in multiple subsequent pipline builds using the same C# assembly versions. + # The package versions will not be affected and will show the complete + # $(Build.BuildNumber) values. + - name: assemblyBuildNumber + value: $[ split(variables['Build.BuildNumber'], '.')[0] ] + + # ---------------------------------------------------------------------------- + # Abstractions Package Versions + # + # These are version values that will be used by the official build. They + # should be updated after each release to reflect the next release's versions. + + # The NuGet package version for GA releases (non-preview). + - name: abstractionsPackageVersion + value: '1.0.0' + + # The NuGet package version for preview releases. + - name: abstractionsPackagePreviewVersion + value: 1.0.0-preview1.$(Build.BuildNumber) + + # The AssemblyFileVersion for all assemblies in the Abstractions package. + # + - name: abstractionsAssemblyFileVersion + value: 1.0.0.$(assemblyBuildNumber) + # Update this after every release. This is used to generate the MDS NuGet package version. - name: Major value: '7' @@ -40,11 +70,11 @@ variables: - name: Revision value: '1' - - name: NugetPackageVersion + - name: mdsPackageVersion value: $(Major).$(Minor).$(Patch) - - name: PreviewNugetPackageVersion + - name: previewMdsPackageVersion value: $(Major).$(Minor).$(Patch)$(Preview)$(Revision).$(Build.BuildNumber) - - name: AssemblyFileVersion - value: '$(Major).$(Minor)$(Patch).$(Build.BuildNumber)' + - name: mdsAssemblyFileVersion + value: $(Major).$(Minor).$(Patch).$(assemblyBuildNumber) - name: nuspecPath value: '$(REPOROOT)/tools/specs/Microsoft.Data.SqlClient.nuspec' diff --git a/eng/pipelines/libraries/mds-validation-variables.yml b/eng/pipelines/libraries/mds-validation-variables.yml index d7723a059f..93dc0804ff 100644 --- a/eng/pipelines/libraries/mds-validation-variables.yml +++ b/eng/pipelines/libraries/mds-validation-variables.yml @@ -13,7 +13,7 @@ variables: - name: extractedNugetRootPath value: $(Build.SourcesDirectory)\$(TempFolderName)\Microsoft.Data.SqlClient - name: extractedNugetPath - value: $(extractedNugetRootPath).$(NugetPackageVersion) + value: $(extractedNugetRootPath).$(mdsPackageVersion) - name: expectedFolderNames value: lib,ref,runtimes - name: expectedDotnetVersions diff --git a/eng/pipelines/stages/build-abstractions-package-ci-stage.yml b/eng/pipelines/stages/build-abstractions-package-ci-stage.yml new file mode 100644 index 0000000000..2a9268e737 --- /dev/null +++ b/eng/pipelines/stages/build-abstractions-package-ci-stage.yml @@ -0,0 +1,119 @@ +################################################################################ +# 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 the Abstractions package, runs tests, and publishes the +# resulting NuGet packages as pipeline artifacts. +# +# The NuGet packages have the following properties: +# +# Name: Microsoft.Data.SqlClient.Extensions.Abstractions +# Version: ${{ abstractionsPackageVersion }} (from parameter) +# +# The following NuGet packages are published: +# +# Microsoft.Data.SqlClient.Extensions.Abstractions..nupkg +# Microsoft.Data.SqlClient.Extensions.Abstractions..snupkg (symbols) +# +# The packages are published to pipeline artifacts with the name specified by +# the ${{ artifactName }} parameter. +# +# This template defines a stage named 'build_abstractions_package_stage' that +# can be depended on by downstream stages. + +parameters: + + # The version to apply to the NuGet package and DLLs. + - name: abstractionsPackageVersion + type: string + + # The name of the pipeline artifact to publish. + - name: artifactName + type: string + default: Abstractions.Artifact + + # The type of build to produce (Release or Debug) + - name: buildConfiguration + type: string + default: Release + values: + - Release + - Debug + + # The verbosity level for the dotnet CLI commands. + - name: verbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + +stages: + + - stage: build_abstractions_package_stage + displayName: Build Abstractions Package + + jobs: + + # ------------------------------------------------------------------------ + # Build and test on Linux. + + - template: ../jobs/test-abstractions-package-ci-job.yml@self + parameters: + jobNameSuffix: linux + displayNamePrefix: Linux + poolName: Azure Pipelines + vmImage: ubuntu-latest + buildConfiguration: ${{ parameters.buildConfiguration }} + netRuntimes: [net8.0, net9.0] + netFrameworkRuntimes: [] + verbosity: ${{ parameters.verbosity }} + + # ------------------------------------------------------------------------ + # Build and test on Windows + + - template: ../jobs/test-abstractions-package-ci-job.yml@self + parameters: + jobNameSuffix: windows + displayNamePrefix: Win + poolName: Azure Pipelines + vmImage: windows-latest + buildConfiguration: ${{ parameters.buildConfiguration }} + netRuntimes: [net8.0, net9.0] + netFrameworkRuntimes: [net462, net47, net471, net472, net48, net481] + verbosity: ${{ parameters.verbosity }} + + # ------------------------------------------------------------------------ + # Build and test on macOS. + + - template: ../jobs/test-abstractions-package-ci-job.yml + parameters: + jobNameSuffix: macos + displayNamePrefix: macOS + poolName: Azure Pipelines + vmImage: macos-latest + buildConfiguration: ${{ parameters.buildConfiguration }} + netRuntimes: [net8.0, net9.0] + netFrameworkRuntimes: [] + verbosity: ${{ parameters.verbosity }} + + # ------------------------------------------------------------------------ + # Create and publish the NuGet package. + + - template: ../jobs/pack-abstractions-package-ci-job.yml@self + parameters: + artifactName: ${{ parameters.artifactName }} + buildConfiguration: ${{ parameters.buildConfiguration }} + abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} + verbosity: ${{ parameters.verbosity }} + dependsOn: + # We depend on all of the test jobs to ensure the tests pass before + # producing the NuGet package. + - test_abstractions_package_job_linux + - test_abstractions_package_job_windows + - test_abstractions_package_job_macos diff --git a/eng/pipelines/steps/compound-build-akv-step.yml b/eng/pipelines/steps/compound-build-akv-step.yml index 906dcfaf72..a547604a2c 100644 --- a/eng/pipelines/steps/compound-build-akv-step.yml +++ b/eng/pipelines/steps/compound-build-akv-step.yml @@ -7,16 +7,16 @@ # @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. -# @TODO: NugetPackageVersion should not be used for MDS package version +# @TODO: mdsPackageVersion should not be used for MDS package version parameters: - - name: assemblyFileVersion + - name: akvPackageVersion type: string - - name: buildConfiguration + - name: assemblyFileVersion type: string - - name: mdsPackageVersion + - name: buildConfiguration type: string steps: @@ -46,7 +46,7 @@ steps: msbuildArguments: >- -t:BuildAkv -p:AssemblyFileVersion=${{ parameters.assemblyFileVersion }} - -p:NugetPackageVersion=${{ parameters.mdsPackageVersion }} + -p:AkvPackageVersion=${{ parameters.akvPackageVersion }} -p:ReferenceType=Package -p:SigningKeyPath=$(Agent.TempDirectory)/netfxKeypair.snk diff --git a/eng/pipelines/steps/roslyn-analyzers-akv-step.yml b/eng/pipelines/steps/roslyn-analyzers-akv-step.yml index 0e05177d5a..c7acc84088 100644 --- a/eng/pipelines/steps/roslyn-analyzers-akv-step.yml +++ b/eng/pipelines/steps/roslyn-analyzers-akv-step.yml @@ -9,10 +9,10 @@ # affect >1 project at a time. parameters: - - name: buildConfiguration + - name: akvPackageVersion type: string - - name: mdsPackageVersion + - name: buildConfiguration type: string steps: @@ -25,7 +25,7 @@ steps: $(REPO_ROOT)/build.proj -t:BuildAkv -p:Configuration=${{ parameters.buildConfiguration }} - -p:NugetPackageVersion=${{ parameters.mdsPackageVersion }} + -p:AkvPackageVersion=${{ parameters.akvPackageVersion }} -p:ReferenceType=Package msBuildVersion: 17.0 setupCommandLinePicker: vs2022 diff --git a/eng/pipelines/variables/akv-official-variables.yml b/eng/pipelines/variables/akv-official-variables.yml index aaf8de7c5e..b22044548d 100644 --- a/eng/pipelines/variables/akv-official-variables.yml +++ b/eng/pipelines/variables/akv-official-variables.yml @@ -37,7 +37,5 @@ variables: # Compound Variables --------------------------------------------------- - name: assemblyFileVersion value: '${{ variables.versionMajor }}.${{ variables.versionMinor }}${{ variables.versionPatch }}.$(Build.BuildNumber)' - - name: nugetPackageVersion + - name: akvPackageVersion value: '${{ variables.versionMajor }}.${{ variables.versionMinor }}.${{ variables.versionPatch }}${{ variables.versionPreview }}' - - diff --git a/release-notes/7.0/7.0.0-preview1.md b/release-notes/7.0/7.0.0-preview1.md new file mode 100644 index 0000000000..045d3e9857 --- /dev/null +++ b/release-notes/7.0/7.0.0-preview1.md @@ -0,0 +1,139 @@ +# Release Notes + +## Preview Release 7.0.0-preview1.25257.1 - 2025-09-12 + +This update brings the following changes since the [6.1.0](../6.1/6.1.0.md) +release: + +### Breaking Changes + +- Removed `Constrained Execution Region` error handling blocks and associated + `SqlConnection` cleanup which may affect how potentially-broken connections + are expunged from the pool. + ([#3535](https://github.com/dotnet/SqlClient/pull/3535)) + +### Bug Fixes + +- Packet multiplexing disabled by default, and several bug fixes. + ([#3534](https://github.com/dotnet/SqlClient/pull/3534), + [#3537](https://github.com/dotnet/SqlClient/pull/3537)) + +### Added + +- `SqlColumnEncryptionCertificateStoreProvider` now works on Windows, Linux, + and macOS. + ([#3014](https://github.com/dotnet/SqlClient/pull/3014)) + +### Changed + +- Updated `SqlVector.Null` to return a nullable `SqlVector` instance in the + reference API to match the implementation. + ([#3521](https://github.com/dotnet/SqlClient/pull/3521)) + +- Performance improvements for all built-in + `SqlColumnEncryptionKeyStoreProvider` implementations. + ([#3554](https://github.com/dotnet/SqlClient/pull/3554)) + +- Various test improvements. + ([#3456](https://github.com/dotnet/SqlClient/pull/3456), + [#2968](https://github.com/dotnet/SqlClient/pull/2968), + [#3458](https://github.com/dotnet/SqlClient/pull/3458), + [#3494](https://github.com/dotnet/SqlClient/pull/3494), + [#3559](https://github.com/dotnet/SqlClient/pull/3559), + [#3575](https://github.com/dotnet/SqlClient/pull/3575)) + +- Codebase merge project and related cleanup. + ([#3436](https://github.com/dotnet/SqlClient/pull/3436), + [#3434](https://github.com/dotnet/SqlClient/pull/3434), + [#3448](https://github.com/dotnet/SqlClient/pull/3448), + [#3454](https://github.com/dotnet/SqlClient/pull/3454), + [#3462](https://github.com/dotnet/SqlClient/pull/3462), + [#3435](https://github.com/dotnet/SqlClient/pull/3435), + [#3492](https://github.com/dotnet/SqlClient/pull/3492), + [#3473](https://github.com/dotnet/SqlClient/pull/3473), + [#3469](https://github.com/dotnet/SqlClient/pull/3469), + [#3394](https://github.com/dotnet/SqlClient/pull/3394), + [#3493](https://github.com/dotnet/SqlClient/pull/3493), + [#3593](https://github.com/dotnet/SqlClient/pull/3593)) + +- Documentation improvements. + ([#3490](https://github.com/dotnet/SqlClient/pull/3490)) + +- Updated `Azure.Identity` dependency to v1.14.2. + ([#3538](https://github.com/dotnet/SqlClient/pull/3538)) + +## Contributors + +We thank the following public contributors. Their efforts toward this project +are very much appreciated. + +- [edwardneal](https://github.com/edwardneal) +- [emmanuel-ferdman](https://github.com/emmanuel-ferdman) +- [ErikEJ](https://github.com/ErikEJ) +- [twsouthwick](https://github.com/twsouthwick) +- [Wraith2](https://github.com/Wraith2) + +### New Contributors + +- [frankbuckley](https://github.com/frankbuckley) made their first contribution + in [#3521](https://github.com/dotnet/SqlClient/pull/3521) + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) +- .NET 8.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) + +### Dependencies + +#### .NET Standard 2.0 + +- Azure.Identity 1.14.2 +- Microsoft.Bcl.Cryptography 9.0.5 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 9.0.5 +- Microsoft.IdentityModel.JsonWebTokens 7.7.1 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.7.1 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 9.0.5 +- System.Security.Cryptography.Pkcs 9.0.5 +- System.Text.Json 9.0.5 + +#### .NET Framework 4.6.2+ + +- Azure.Identity 1.14.2 +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.SNI 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 7.7.1 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.7.1 +- System.Buffers 4.5.1 +- System.Data.Common 4.3.0 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Encodings.Web 8.0.0 +- System.Text.Json 8.0.5 + +#### .NET 8.0 + +- Azure.Identity 1.14.2 +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 7.7.1 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.7.1 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 8.0.1 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Json 8.0.5 + +#### .NET 9.0 + +- Azure.Identity 1.14.2 +- Microsoft.Bcl.Cryptography 9.0.5 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 9.0.5 +- Microsoft.IdentityModel.JsonWebTokens 7.7.1 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.7.1 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 9.0.5 +- System.Security.Cryptography.Pkcs 9.0.5 +- System.Text.Json 9.0.5 diff --git a/release-notes/7.0/README.md b/release-notes/7.0/README.md new file mode 100644 index 0000000000..6cbd345392 --- /dev/null +++ b/release-notes/7.0/README.md @@ -0,0 +1,7 @@ +# Microsoft.Data.SqlClient 7.0 Releases + +The following Microsoft.Data.SqlClient 7.0 releases have been shipped: + +| Release Date | Version | Notes | +| :-- | :-- | :--: | +| 2025-09-12 | 7.0.0-preview1.25257.1 | [Release Notes](7.0.0-preview1.md) | diff --git a/release-notes/README.md b/release-notes/README.md index 612316204e..f2d7c0b452 100644 --- a/release-notes/README.md +++ b/release-notes/README.md @@ -4,6 +4,7 @@ The latest stable release is [Microsoft.Data.SqlClient 6.0](6.0). ## Release Information +- [Microsoft.Data.SqlClient 7.0](7.0) - [Microsoft.Data.SqlClient 6.1](6.1) - [Microsoft.Data.SqlClient 6.0](6.0) - [Microsoft.Data.SqlClient 5.2](5.2) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 4c210df737..69143965a9 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -20,6 +20,21 @@ > msbuild -p:configuration=Release --> Project + + + $(AllowedOutputExtensionsInPackageBuildOutputFolder) + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 6ceddccd37..6cf7a6bc93 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -7,6 +7,13 @@ + + + + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/Abstractions.slnx b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/Abstractions.slnx new file mode 100644 index 0000000000..9632efff88 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/Abstractions.slnx @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/README.md b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/README.md new file mode 100644 index 0000000000..d2a24b898c --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/README.md @@ -0,0 +1,285 @@ +# MDS Azure Extension Design + +## Overview + +For the MDS 7.0.0 release, we are proposing the following package architecture +changes that will decouple several large dependencies from MDS and move them +into a new `Azure` extension package: + +- Create a new `Abstractions` package that all other MDS packages depend on. + - This will contain types and definitions common to the other MDS packages, + such as base classes, enums, delegates, etc. +- Create a new `Azure` package that will own the following implementations: + - Azure Authentication + - Azure Attestation + - Azure Key Vault interactions +- Move the above implementations out of MDS and into the new `Azure` package. +- Move the existing `AzureKeyVaultProvider` (AKV) implementation into the new + `Azure` extension package. + +This will reduce the main MDS package dependency tree along with a moderate +package size reduction. + +## Motivation + +Issue: [#1108](https://github.com/dotnet/SqlClient/issues/1108) + +Customers and the developer community have voiced concerns with MDS being +tightly coupled to Azure dependencies. Many customers do not use Azure and do +not want to deploy unnecessary DLLs with their applications. + +Moving the Azure dependent implementations into a separate `Azure` extension +package achieves two goals: + +- Remove Azure packages as direct dependencies of MDS and reduce the MDS + dependency tree. +- Clearly expose existing MDS extension points, prove their functionality, and + demonstrate how to use them. + +The following dependencies will be removed from the main MDS package: + +- `Azure.Identity` + - `Azure.Core` (transitive) + - `Microsoft.Identity.Client` (transitive) +- `Microsoft.IdentityModel.JsonWebTokens` + - `Microsoft.IdentityModel.Tokens` (transitive) + - `Microsoft.IdentityModel.Logging` (transitive) +- `Microsoft.IdentityModel.Protocols.OpenIdConnect` + - `Microsoft.IdentityModel.Protocols` (transitive) + +The following dependencies will be removed from the AKV Provider package: + +- `Azure.Core` +- `Azure.Security.KeyVault.Keys` + +## Package Architecture + +```mermaid +classDiagram +class MDS +class MDS.Extensions.Abstractions +class MDS.Extensions.Azure +class AKV Provider + +MDS --> MDS.Extensions.Abstractions +MDS ..> MDS.Extensions.Azure +MDS ..> AKV Provider +MDS.Extensions.Azure --> MDS.Extensions.Abstractions +AKV Provider --> MDS.Extensions.Azure + +MDS: Depend on MDS.Extensions.Abstractions +MDS: Load Azure or AKV assembly +MDS.Extensions.Abstractions: Azure Authentication Types +MDS.Extensions.Abstractions: Azure Attestation Types +MDS.Extensions.Abstractions: Azure Key Vault Types +MDS.Extensions.Azure: Depend on MDS.Extensions.Abstractions +MDS.Extensions.Azure: Authentication Implementation +MDS.Extensions.Azure: Attestation Implementation +MDS.Extensions.Azure: Key Vault Implementation +AKV Provider: Depend on MDS.Extensions.Azure +``` + +In previous MDS versions, the AKV package depended directly on the main MDS +package through a ranged version (for example [6.0.0, 7.0.0) - all 6.x +versions). With the new package architecture this is no longer the case. +Extension packages will not depend on the main MDS package, nor will the main +MDS package depend on any extension packages. All dependencies between MDS and +its extensions will occur through the `Abstractions` package. + +This new looser coupling gives applications the flexibility to depend on only +the main MDS package, or on MDS and a subset of it extension packages if +desired. + +## Consuming + +There are several ways that applications may consume MDS and its extensions: + +- MDS with without Azure features +- MDS with MDS-supplied Azure features +- MDS with externally supplied Azure features + +Applications never need to directly depend on the `Abstractions` base package. +This will be transitively depended on by other MDS packages. + +### Without Azure Features + +Applications that do not use any Azure features will no longer bring in those +unwanted dependencies transitively. Simply include the main MDS package by +itself: + +```xml + + + +``` + +Calls to MDS APIs that require Azure features will throw an exception, since +no Azure feature implementation is present. + +### With MDS Azure Features + +Applications that wish to use MDS-supplied Azure features will need to include +the new `Azure` extension package as a direct dependency alongside the main MDS +package: + +```xml + + + + +``` + +MDS will automatically detect the `Azure` extension assemblies and load them. + +### With External Azure Features + +Applications that wish to use Azure features supplied by another (non-MDS) +package will need to include that package as a direct dependency alongside the +main MDS package: + +```xml + + + + +``` + +Additionally, applications will need to instruct MDS to use the external Azure +feature implementations via the appropriate APIs at runtime: + +- Authentication: [SqlAuthenticationProvider](https://learn.microsoft.com/en-us/dotnet/api/microsoft.data.sqlclient.sqlauthenticationprovider?view=sqlclient-dotnet-core-6.0) +- Attestation: _**New API will be exposed.**_ +- Key Valut: [SqlColumnEncryptionKeyStoreProvider](https://learn.microsoft.com/en-us/dotnet/api/microsoft.data.sqlclient.sqlcolumnencryptionkeystoreprovider?view=sqlclient-dotnet-core-6.0) + +## Versioning Strategy + +The MDS suite of packages will be versioned independently. This provides +flexibility to update APIs and implementations for packages as needed, avoiding +unnecessary version bumps and releases. The initial release of these packages +will have the following versions: + +|Package|Version|Comment| +|-|-|-| +|`Microsoft.Data.SqlClient.Extensions.Abstractions`|1.0.0|First version of this package.| +|`Microsoft.Data.SqlClient`|7.0.0|Major version bump due to breaking changes described in this document.| +|`Microsoft.Data.SqlClient.Extensions.Azure`|1.0.0|First version of this package.| +|`Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider`|7.0.0|_**Deprecated.**_| + +Going forward, each package will be versioned appropriately based on the nature +of the changes included with subsequent releases. + +**Note**: The `AzureKeyVaultProvider` package will remain at 7.0.0. It will be +deprecated and eventually removed, as it has been replaced by the `Azure` +extension package. + +## Intradependence + +The main MDS package and the new `Azure` package will depend on the +`Abstractions` package. When APIs are added, modified, or removed from the +`Abstractions` package, corresponding changes will be made to the dependent +packages as well. Those dependent packages will then take a strict dependency +on the appropriate `Abstractions` package version. This ensures that only +compatible extensions package versions can co-exist with the main MDS package. + +For example, imagine that a new extensible conenction pooling feature is added +to MDS. The `Abstractions` package would be updated to include any new pooling +APIs, the main MDS package would be updated to accept extensible pooling, and +the new pooling implementation would be included in a new `ConnectionPooling` +extension package. The versions of these packages would look something like +this: + +|Package|Version| +|-|-| +|`Microsoft.Data.SqlClient.Extensions.Abstractions`|1.1.0| +|`Microsoft.Data.SqlClient`|7.1.0| +|`Microsoft.Data.SqlClient.Extensions.ConnectionPooling`|1.0.0| + +Both the main MDS package and the new `ConnectionPooling` package would depend +on `Abstractions` v1.1.0. + +An application wishing to use the new `ConnectionPooling` v1.0.0 package must +also update the main MDS package to v7.1.0. The applictaion would not be able +to use `ConnectionPooling` v1.0.0 and MDS v7.0.0. + +## Backwards Compatibility + +There are several backwards compatibility scenarios to consider for applications +that rely on MDS Azure features currently implemented in the main MDS package +and the AKV package. The new extensions package architecture aims to reduce the +friction for these apps, but not all scenarios will be seamless. + +All of the scenarios below assume that the application is upgrading to the +latest versions of MDS packages. + +### Apps using MDS Azure Authentication + +Applications currently using the MDS-supplied Azure Authentication features will +need to add a dependency on the `Azure` extension package to their project +alongside the main MDS package: + +```xml + + + + +``` + +All Azure Authentication namespaces and types will remain the same, so this +should be the only change necessary for applications. + +### Apps using MDS Azure Attestation + +Applications currently using the MDS-supplied Azure Attestation features will +need to add a dependency on the `Azure` extension package to their project +alongside the main MDS package: + +```xml + + + + +``` + +All Azure Attestation namespaces and types will remain the same, so this should +be the only change necessary for applications. + +### Apps using AKV Provider + +Applications currently using the MDS-supplied AKV provider will have two options +when upgrading to MDS v7.0.0. Both options rely on the main MDS package finding +and loading an appropriate DLL (assembly) at runtime. The absence of an +appropriate DLL will cause Azure Key Vault operations to throw an exception. + +#### Use Azure Extension + +This is the preferred approach. The application would be updated to depend +on the main MDS package and the `Azure` extension package: + +```xml + + + + +``` + +The `Azure` extension package will contain the same namespaces and types as the +current AKV provider and will be a drop-in replacement. The main MDS v7.0.0 +package will look for the `Azure` extension assembly and automatically load it. + +#### Use AKV Provider v7.0.0 + +This is a temporary solution. The AKV provider v7.0.0 will be marked as +deprecated and removed entirely at some point in the future. The applictaion +would remain dependent on the AKV provider, but must update to the v7.0.0 +package. Previous AKV package versions do not support main MDS package versions +beyond the v6.x range. + +```xml + + + + +``` + +This AKV Provider v7.0.0 package will be empty and simply depend on the `Azure` +extension package to transitively provide the Azure Key Vault features. diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/Sample.xml b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/Sample.xml new file mode 100644 index 0000000000..8d5f5c44d5 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/Sample.xml @@ -0,0 +1,18 @@ + + + + + + Sample class to demonstrate packaging and pipelines. + + + + Construct with a name. + The name. + + + Gets the name. + The name. + + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj new file mode 100644 index 0000000000..b214f69b19 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj @@ -0,0 +1,114 @@ + + + + + + + + + + + 1 + + + + $(AbstractionsAssemblyFileVersion) + + $(AbstractionsPackageVersion.Split('-')[0]) + + $(DefaultMajorVersion).0.0.$(BuildNumber) + + + $(AbstractionsPackageVersion) + $(DefaultMajorVersion).0.0.$(BuildNumber)-dev + + + + + netstandard2.0 + + + + + enable + enable + + + + + Microsoft.Data.SqlClient.Extensions.Abstractions + Microsoft.Data.SqlClient.Extensions.Abstractions + + + $(DefaultMajorVersion).0.0.0 + + $(OurAssemblyFileVersion) + $(OurAssemblyFileVersion) + $(OurPackageVersion) + + + + + <_Parameter1>true + + + + + + + + + $(AssemblyName) + $(OurPackageVersion) + $(PackagesDir) + true + snupkg + + + $(OriginalAllowedOutputExtensions) + + Microsoft Corporation + Microsoft Corporation + Microsoft.Data.SqlClient Extensions Abstractions + https://github.com/dotnet/SqlClient + MIT + dotnet.png + + + + + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Sample.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Sample.cs new file mode 100644 index 0000000000..bf22119436 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Sample.cs @@ -0,0 +1,20 @@ +namespace Microsoft.Data.SqlClient.Extensions.Abstractions; + +/// +public class Sample +{ + /// + public Sample(string name) + { + Name = name; + } + + /// + public string Name { get; private set; } + + // Update the name. + internal void SetName(string name) + { + Name = name; + } +} diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj new file mode 100644 index 0000000000..118b215737 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj @@ -0,0 +1,26 @@ + + + + net462;net47;net471;net472;net48;net481;net8.0;net9.0 + enable + enable + false + true + Microsoft.Data.SqlClient.Extensions.Abstractions.Test + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SampleTest.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SampleTest.cs new file mode 100644 index 0000000000..ab8e9da052 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SampleTest.cs @@ -0,0 +1,19 @@ +namespace Microsoft.Data.SqlClient.Extensions.Abstractions.Test; + +public class SampleTest +{ + [Fact] + public void Construction() + { + Assert.Equal("test", new Sample("test").Name); + } + + [Fact] + public void SetName() + { + var sample = new Sample("test"); + Assert.Equal("test", sample.Name); + sample.SetName("new name"); + Assert.Equal("new name", sample.Name); + } +} diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln index e4d29d999c..2b8e583a8d 100644 --- a/src/Microsoft.Data.SqlClient.sln +++ b/src/Microsoft.Data.SqlClient.sln @@ -1,6 +1,7 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.0.31912.275 +VisualStudioVersion = 17.14.36203.30 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient", "Microsoft.Data.SqlClient\netfx\src\Microsoft.Data.SqlClient.csproj", "{407890AC-9876-4FEF-A6F1-F36A876BAADE}" EndProject @@ -303,6 +304,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.SqlClient.Un EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Microsoft.Data.SqlClient\tests\Common\Common.csproj", "{67128EC0-30F5-6A98-448B-55F88A1DE707}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlClient.Extensions", "Microsoft.Data.SqlClient.Extensions", "{19F1F1E5-3013-7660-661A-2A15F7D606C1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Abstractions", "Abstractions", "{556B486E-F9B0-7EA9-6A25-DA560C312761}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{210228A5-979A-DE06-EE1F-B35C65E1583C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abstractions", "Microsoft.Data.SqlClient.Extensions\Abstractions\src\Abstractions.csproj", "{B21E7C94-D805-427E-928A-8DE8EA2F08CC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{59667E4C-0BD2-9F48-FB50-9E55DD8B1011}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abstractions.Test", "Microsoft.Data.SqlClient.Extensions\Abstractions\test\Abstractions.Test.csproj", "{04ACBF75-CFF2-41AB-B652-776BC0533490}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -571,6 +584,30 @@ Global {67128EC0-30F5-6A98-448B-55F88A1DE707}.Release|x64.Build.0 = Release|x64 {67128EC0-30F5-6A98-448B-55F88A1DE707}.Release|x86.ActiveCfg = Release|x86 {67128EC0-30F5-6A98-448B-55F88A1DE707}.Release|x86.Build.0 = Release|x86 + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Debug|x64.ActiveCfg = Debug|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Debug|x64.Build.0 = Debug|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Debug|x86.ActiveCfg = Debug|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Debug|x86.Build.0 = Debug|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Release|Any CPU.Build.0 = Release|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Release|x64.ActiveCfg = Release|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Release|x64.Build.0 = Release|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Release|x86.ActiveCfg = Release|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Release|x86.Build.0 = Release|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Debug|Any CPU.Build.0 = Debug|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Debug|x64.ActiveCfg = Debug|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Debug|x64.Build.0 = Debug|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Debug|x86.ActiveCfg = Debug|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Debug|x86.Build.0 = Debug|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Release|Any CPU.Build.0 = Release|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Release|x64.ActiveCfg = Release|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Release|x64.Build.0 = Release|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Release|x86.ActiveCfg = Release|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -621,6 +658,11 @@ Global {AD738BD4-6A02-4B88-8F93-FBBBA49A74C8} = {4CAE9195-4F1A-4D48-854C-1C9FBC512C66} {4461063D-2F2B-274C-7E6F-F235119D258E} = {0CC4817A-12F3-4357-912C-09315FAAD008} {67128EC0-30F5-6A98-448B-55F88A1DE707} = {0CC4817A-12F3-4357-912C-09315FAAD008} + {556B486E-F9B0-7EA9-6A25-DA560C312761} = {19F1F1E5-3013-7660-661A-2A15F7D606C1} + {210228A5-979A-DE06-EE1F-B35C65E1583C} = {556B486E-F9B0-7EA9-6A25-DA560C312761} + {B21E7C94-D805-427E-928A-8DE8EA2F08CC} = {210228A5-979A-DE06-EE1F-B35C65E1583C} + {59667E4C-0BD2-9F48-FB50-9E55DD8B1011} = {556B486E-F9B0-7EA9-6A25-DA560C312761} + {04ACBF75-CFF2-41AB-B652-776BC0533490} = {59667E4C-0BD2-9F48-FB50-9E55DD8B1011} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {01D48116-37A2-4D33-B9EC-94793C702431} 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 0944a4aea0..4b658f409a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -46,7 +46,20 @@ - + + + + + + + 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 395f3df11d..19951ebc96 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -822,7 +822,6 @@ - @@ -1068,6 +1067,19 @@ + + + + + + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs index d43b71ff32..5fd97e3b13 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs @@ -107,24 +107,11 @@ protected override void AfterCleared(SqlCommand owner) } } - private int? _commandTimeout; - private UpdateRowSource _updatedRowSource = UpdateRowSource.Both; - private bool _designTimeInvisible; - /// /// Indicates if the column encryption setting was set at-least once in the batch rpc mode, when using AddBatchCommand. /// private bool _wasBatchModeColumnEncryptionSettingSetOnce; - /// - /// Column Encryption Override. Defaults to SqlConnectionSetting, in which case - /// it will be Enabled if SqlConnectionOptions.IsColumnEncryptionSettingEnabled = true, Disabled if false. - /// This may also be used to set other behavior which overrides connection level setting. - /// - private SqlCommandColumnEncryptionSetting _columnEncryptionSetting = SqlCommandColumnEncryptionSetting.UseConnectionSetting; - - internal SqlDependency _sqlDep; - #if DEBUG /// /// Force the client to sleep during sp_describe_parameter_encryption in the function TryFetchInputParameterEncryptionInfo. @@ -157,10 +144,6 @@ protected override void AfterCleared(SqlCommand owner) internal static readonly Action s_cancelIgnoreFailure = CancelIgnoreFailureCallback; - private int _preparedConnectionCloseCount = -1; - private int _preparedConnectionReconnectCount = -1; - - private SqlParameterCollection _parameters; private _SqlRPC[] _rpcArrayOf1 = null; // Used for RPC executes private _SqlRPC _rpcForEncryption = null; // Used for sp_describe_parameter_encryption RPC executes @@ -192,23 +175,6 @@ private bool ShouldCacheEncryptionMetadata internal static int DebugForceAsyncWriteDelay { get; set; } #endif - /// - /// Return if column encryption setting is enabled. - /// The order in the below if is important since _activeConnection.Parser can throw if the - /// underlying tds connection is closed and we don't want to change the behavior for folks - /// not trying to use transparent parameter encryption i.e. who don't use (SqlCommandColumnEncryptionSetting.Enabled or _activeConnection.IsColumnEncryptionSettingEnabled) here. - /// - internal bool IsColumnEncryptionEnabled - { - get - { - return (_columnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled - || (_columnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && _activeConnection.IsColumnEncryptionSettingEnabled)) - && _activeConnection.Parser != null - && _activeConnection.Parser.IsColumnEncryptionSupported; - } - } - internal bool ShouldUseEnclaveBasedWorkflow => (!string.IsNullOrWhiteSpace(_activeConnection.EnclaveAttestationUrl) || Connection.AttestationProtocol == SqlConnectionAttestationProtocol.None) && IsColumnEncryptionEnabled; @@ -324,33 +290,13 @@ private AsyncState CachedAsyncState } } - // sql reader will pull this value out for each NextResult call. It is not cumulative - // _rowsAffected is cumulative for ExecuteNonQuery across all rpc batches - internal int _rowsAffected = -1; // rows affected by the command - // number of rows affected by sp_describe_parameter_encryption. // The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise. private int _rowsAffectedBySpDescribeParameterEncryption = -1; - - private SqlNotificationRequest _notification; - - // transaction support - private SqlTransaction _transaction; - - private StatementCompletedEventHandler _statementCompletedEventHandler; - - // Volatile bool used to synchronize with cancel thread the state change of an executing - // command going from pre-processing to obtaining a stateObject. The cancel synchronization - // we require in the command is only from entering an Execute* API to obtaining a - // stateObj. Once a stateObj is successfully obtained, cancel synchronization is handled - // by the stateObject. - private volatile bool _pendingCancel; - - private bool _batchRPCMode; + private List<_SqlRPC> _RPCList; private _SqlRPC[] _sqlRPCParameterEncryptionReqArray; private int _currentlyExecutingBatch; - private SqlRetryLogicBaseProvider _retryLogicProvider; /// /// This variable is used to keep track of which RPC batch's results are being read when reading the results of @@ -428,238 +374,8 @@ private SqlCommand(SqlCommand from) : this() } } - /// - [DefaultValue(null)] - [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.DbCommand_Connection)] - new public SqlConnection Connection - { - get - { - return _activeConnection; - } - set - { - // Don't allow the connection to be changed while in an async operation. - if (_activeConnection != value && _activeConnection != null) - { - // If new value... - if (CachedAsyncState != null && CachedAsyncState.PendingAsyncOperation) - { - // If in pending async state, throw. - throw SQL.CannotModifyPropertyAsyncOperationInProgress(); - } - } - - // Check to see if the currently set transaction has completed. If so, - // null out our local reference. - if (_transaction != null && _transaction.Connection == null) - { - _transaction = null; - } - - // Command is no longer prepared on new connection, cleanup prepare status - if (IsPrepared) - { - if (_activeConnection != value && _activeConnection != null) - { - try - { - // cleanup - Unprepare(); - } - catch (Exception) - { - // we do not really care about errors in unprepare (may be the old connection went bad) - } - finally - { - // clean prepare status (even successful Unprepare does not do that) - _prepareHandle = s_cachedInvalidPrepareHandle; - _execType = EXECTYPE.UNPREPARED; - } - } - } - _activeConnection = value; - SqlClientEventSource.Log.TryTraceEvent("SqlCommand.Set_Connection | API | ObjectId {0}, Client Connection Id {1}", ObjectID, value?.ClientConnectionId); - } - } - - /// - protected override DbConnection DbConnection - { - get - { - return Connection; - } - set - { - Connection = (SqlConnection)value; - } - } - - private SqlInternalConnectionTds InternalTdsConnection - { - get - { - return (SqlInternalConnectionTds)_activeConnection.InnerConnection; - } - } - private bool IsProviderRetriable => SqlConfigurableRetryFactory.IsRetriable(RetryLogicProvider); - /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public SqlRetryLogicBaseProvider RetryLogicProvider - { - get - { - if (_retryLogicProvider == null) - { - _retryLogicProvider = SqlConfigurableRetryLogicManager.CommandProvider; - } - return _retryLogicProvider; - } - set - { - _retryLogicProvider = value; - } - } - - /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] // MDAC 90471 - [ResCategory(StringsHelper.ResourceNames.DataCategory_Notification)] - [ResDescription(StringsHelper.ResourceNames.SqlCommand_Notification)] - public SqlNotificationRequest Notification - { - get - { - return _notification; - } - set - { - _sqlDep = null; - _notification = value; - SqlClientEventSource.Log.TryTraceEvent("SqlCommand.Set_Notification | API | Object Id {0}", ObjectID); - } - } - - internal SqlStatistics Statistics - { - get - { - if (_activeConnection != null) - { - if (_activeConnection.StatisticsEnabled || - s_diagnosticListener.IsEnabled(SqlClientCommandAfter.Name)) - { - return _activeConnection.Statistics; - } - } - return null; - } - } - - /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - [ResDescription(StringsHelper.ResourceNames.DbCommand_Transaction)] - new public SqlTransaction Transaction - { - get - { - // if the transaction object has been zombied, just return null - if (_transaction != null && _transaction.Connection == null) - { - _transaction = null; - } - return _transaction; - } - set - { - // Don't allow the transaction to be changed while in an async operation. - if (_transaction != value && _activeConnection != null) - { - // If new value... - if (CachedAsyncState.PendingAsyncOperation) - { - // If in pending async state, throw - throw SQL.CannotModifyPropertyAsyncOperationInProgress(); - } - } - _transaction = value; - SqlClientEventSource.Log.TryTraceEvent("SqlCommand.Set_Transaction | API | Object Id {0}, Internal Transaction Id {1}, Client Connection Id {2}", ObjectID, value?.InternalTransaction?.TransactionId, Connection?.ClientConnectionId); - } - } - - /// - protected override DbTransaction DbTransaction - { - get - { - return Transaction; - } - set - { - Transaction = (SqlTransaction)value; - SqlClientEventSource.Log.TryTraceEvent("SqlCommand.Set_DbTransaction | API | Object Id {0}, Client Connection Id {1}", ObjectID, Connection?.ClientConnectionId); - } - } - - /// - [DefaultValue("")] - [RefreshProperties(RefreshProperties.All)] // MDAC 67707 - [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.DbCommand_CommandText)] - public override string CommandText - { - get => _commandText ?? ""; - set - { - if (_commandText != value) - { - PropertyChanging(); - _commandText = value; - } - SqlClientEventSource.Log.TryTraceEvent("SqlCommand.Set_CommandText | API | Object Id {0}, String Value = '{1}', Client Connection Id {2}", ObjectID, value, Connection?.ClientConnectionId); - } - } - - /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.TCE_SqlCommand_ColumnEncryptionSetting)] - public SqlCommandColumnEncryptionSetting ColumnEncryptionSetting => _columnEncryptionSetting; - - /// - [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.DbCommand_CommandTimeout)] - public override int CommandTimeout - { - get - { - return _commandTimeout ?? DefaultCommandTimeout; - } - set - { - if (value < 0) - { - throw ADP.InvalidCommandTimeout(value); - } - - if (value != _commandTimeout) - { - PropertyChanging(); - _commandTimeout = value; - } - - SqlClientEventSource.Log.TryTraceEvent("SqlCommand.Set_CommandTimeout | API | ObjectId {0}, Command Timeout value {1}, Client Connection Id {2}", ObjectID, value, Connection?.ClientConnectionId); - } - } - /// public void ResetCommandTimeout() { @@ -670,142 +386,6 @@ public void ResetCommandTimeout() } } - private int DefaultCommandTimeout - { - get - { - return _activeConnection?.CommandTimeout ?? ADP.DefaultCommandTimeout; - } - } - - /// - [DefaultValue(CommandType.Text)] - [RefreshProperties(RefreshProperties.All)] - [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.DbCommand_CommandType)] - public override CommandType CommandType - { - get - { - CommandType cmdType = _commandType; - return ((0 != cmdType) ? cmdType : CommandType.Text); - } - set - { - if (_commandType != value) - { - switch (value) - { - case CommandType.Text: - case CommandType.StoredProcedure: - PropertyChanging(); - _commandType = value; - break; - case System.Data.CommandType.TableDirect: - throw SQL.NotSupportedCommandType(value); - default: - throw ADP.InvalidCommandType(value); - } - - SqlClientEventSource.Log.TryTraceEvent("SqlCommand.Set_CommandType | API | ObjectId {0}, Command type value {1}, Client Connection Id {2}", ObjectID, (int)value, Connection?.ClientConnectionId); - } - } - } - - // By default, the cmd object is visible on the design surface (i.e. VS7 Server Tray) - // to limit the number of components that clutter the design surface, - // when the DataAdapter design wizard generates the insert/update/delete commands it will - // set the DesignTimeVisible property to false so that cmds won't appear as individual objects - /// - [DefaultValue(true)] - [DesignOnly(true)] - [Browsable(false)] - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool DesignTimeVisible - { - get - { - return !_designTimeInvisible; - } - set - { - _designTimeInvisible = !value; - } - } - - /// - public bool EnableOptimizedParameterBinding { get; set; } - - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] - [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.DbCommand_Parameters)] - new public SqlParameterCollection Parameters - { - get - { - if (_parameters == null) - { - // delay the creation of the SqlParameterCollection - // until user actually uses the Parameters property - _parameters = new SqlParameterCollection(); - } - return _parameters; - } - } - - /// - protected override DbParameterCollection DbParameterCollection - { - get - { - return Parameters; - } - } - - /// - [DefaultValue(UpdateRowSource.Both)] - [ResCategory(StringsHelper.ResourceNames.DataCategory_Update)] - [ResDescription(StringsHelper.ResourceNames.DbCommand_UpdatedRowSource)] - public override UpdateRowSource UpdatedRowSource - { - get - { - return _updatedRowSource; - } - set - { - switch (value) - { - case UpdateRowSource.None: - case UpdateRowSource.OutputParameters: - case UpdateRowSource.FirstReturnedRecord: - case UpdateRowSource.Both: - _updatedRowSource = value; - break; - default: - throw ADP.InvalidUpdateRowSource(value); - } - - SqlClientEventSource.Log.TryTraceEvent("SqlCommand.UpdatedRowSource | API | ObjectId {0}, Updated row source value {1}, Client Connection Id {2}", ObjectID, (int)value, Connection?.ClientConnectionId); - } - } - - /// - [ResCategory(StringsHelper.ResourceNames.DataCategory_StatementCompleted)] - [ResDescription(StringsHelper.ResourceNames.DbCommand_StatementCompleted)] - public event StatementCompletedEventHandler StatementCompleted - { - add - { - _statementCompletedEventHandler += value; - } - remove - { - _statementCompletedEventHandler -= value; - } - } - internal void OnStatementCompleted(int recordCount) { if (0 <= recordCount) @@ -829,12 +409,6 @@ internal void OnStatementCompleted(int recordCount) } } - private void PropertyChanging() - { - // also called from SqlParameterCollection - this.IsDirty = true; - } - // Cancel is supposed to be multi-thread safe. // It doesn't make sense to verify the connection exists or that it is open during cancel // because immediately after checking the connection can be closed or removed via another thread. @@ -6614,25 +6188,6 @@ internal int RowsAffectedByDescribeParameterEncryption } } - internal int InternalRecordsAffected - { - get - { - return _rowsAffected; - } - set - { - if (-1 == _rowsAffected) - { - _rowsAffected = value; - } - else if (0 < value) - { - _rowsAffected += value; - } - } - } - /// /// Clear the state in sqlcommand related to describe parameter encryption RPC requests. /// 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 8fbeb3050c..4d1eb4e75e 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 @@ -833,13 +833,13 @@ private void SendPreLoginHandshake( break; case (int)PreLoginOptions.TRACEID: - FillGuidBytes(_connHandler._clientConnectionId, payload.AsSpan(payloadLength, GUID_SIZE)); + SerializeGuid(_connHandler._clientConnectionId, payload.AsSpan(payloadLength, GUID_SIZE)); payloadLength += GUID_SIZE; offset += GUID_SIZE; optionDataSize = GUID_SIZE; ActivityCorrelator.ActivityId actId = ActivityCorrelator.Next(); - FillGuidBytes(actId.Id, payload.AsSpan(payloadLength, GUID_SIZE)); + SerializeGuid(actId.Id, payload.AsSpan(payloadLength, GUID_SIZE)); payloadLength += GUID_SIZE; payload[payloadLength++] = (byte)(0x000000ff & actId.Sequence); payload[payloadLength++] = (byte)((0x0000ff00 & actId.Sequence) >> 8); @@ -968,7 +968,7 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( if (_physicalStateObj._inBytesPacket > TdsEnums.MAX_PACKET_SIZE || _physicalStateObj._inBytesPacket <= 0) { - throw SQL.ParsingError(); + throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); } byte[] payload = new byte[_physicalStateObj._inBytesPacket]; @@ -1017,7 +1017,7 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( payloadOffset = payload[offset++] << 8 | payload[offset++]; payloadLength = payload[offset++] << 8 | payload[offset++]; - EncryptionOptions serverOption = (EncryptionOptions)payload[payloadOffset]; + EncryptionOptions serverOption = ((EncryptionOptions)payload[payloadOffset]) & EncryptionOptions.OPTIONS_MASK; /* internal enum EncryptionOptions { OFF, @@ -1160,7 +1160,8 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( // Validate Certificate if Trust Server Certificate=false and Encryption forced (EncryptionOptions.ON) from Server. bool shouldValidateServerCert = (_encryptionOption == EncryptionOptions.ON && !trustServerCert) || - (_connHandler._accessTokenInBytes != null && !trustServerCert); + ((_connHandler._accessTokenInBytes != null || _connHandler._accessTokenCallback != null) && !trustServerCert); + uint info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0) | TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE; @@ -1679,9 +1680,12 @@ internal void CheckResetConnection(TdsParserStateObject stateObj) #endif } - // - // Takes a 16 bit short and writes it to the returned buffer. - // + /// + /// Serializes a 16 bit short to the returned buffer. + /// + /// The value to serialize. + /// containing the cached buffer bytes. + /// The serialized 16 bit short. internal byte[] SerializeShort(int v, TdsParserStateObject stateObj) { if (stateObj._bShortBytes == null) @@ -1700,9 +1704,11 @@ internal byte[] SerializeShort(int v, TdsParserStateObject stateObj) return bytes; } - // - // Takes a 16 bit short and writes it. - // + /// + /// Writes a 16 bit short to the wire. + /// + /// The value to write. + /// containing the wire buffer. internal void WriteShort(int v, TdsParserStateObject stateObj) { if ((stateObj._outBytesUsed + 2) > stateObj._outBuff.Length) @@ -1720,27 +1726,97 @@ internal void WriteShort(int v, TdsParserStateObject stateObj) } } + /// + /// Writes a 16 bit unsigned short to the wire. + /// + /// The value to write. + /// containing the wire buffer. internal void WriteUnsignedShort(ushort us, TdsParserStateObject stateObj) { WriteShort((short)us, stateObj); } - // - // Takes a long and writes out an unsigned int - // + /// + /// Serializes a Guid to the specified buffer. + /// + /// The value to serialize. + /// The buffer to serialize to. The size of this buffer must be 16 bytes or larger. + private static void SerializeGuid(in Guid v, Span buffer) + { + Debug.Assert(buffer.Length >= GUID_SIZE); +#if NET + v.TryWriteBytes(buffer, bigEndian: false, out _); +#else + byte[] guidBytes = v.ToByteArray(); + guidBytes.AsSpan().CopyTo(buffer); +#endif + } + + /// + /// Writes a SqlGuid to the wire. + /// + /// The value to write. + /// containing the wire buffer. + private static void WriteGuid(in SqlGuid v, TdsParserStateObject stateObj) + { + Guid innerValue = v.IsNull ? Guid.Empty : v.Value; + + WriteGuid(in innerValue, stateObj); + } + + /// + /// Writes a Guid to the wire. + /// + /// The value to write. + /// containing the wire buffer. + private static void WriteGuid(in Guid v, TdsParserStateObject stateObj) + { + if ((stateObj._outBytesUsed + GUID_SIZE) > stateObj._outBuff.Length) + { + Span buffer = stackalloc byte[GUID_SIZE]; + + SerializeGuid(in v, buffer); + // if all of the guid doesn't fit into the buffer + for (int index = 0; index < buffer.Length; index++) + { + stateObj.WriteByte(buffer[index]); + } + } + else + { + // all of the guid fits into the buffer + SerializeGuid(in v, stateObj._outBuff.AsSpan(stateObj._outBytesUsed, GUID_SIZE)); + stateObj._outBytesUsed += GUID_SIZE; + } + } + + /// + /// Serializes an unsigned 32 bit integer to the returned buffer. + /// + /// The value to serialize. + /// containing the cached buffer bytes. + /// The serialized unsigned 32 bit integer. internal byte[] SerializeUnsignedInt(uint i, TdsParserStateObject stateObj) { return SerializeInt((int)i, stateObj); } + /// + /// Writes an unsigned 32 bit integer to the wire. + /// + /// The value to write. + /// containing the wire buffer. internal void WriteUnsignedInt(uint i, TdsParserStateObject stateObj) { WriteInt((int)i, stateObj); } - // - // Takes an int and writes it as an int. - // + /// + /// Serializes a signed 32 bit integer to the returned buffer. + /// + /// The value to serialize. + /// containing the cached buffer bytes. + /// The serialized signed 32 bit integer. internal byte[] SerializeInt(int v, TdsParserStateObject stateObj) { if (stateObj._bIntBytes == null) @@ -1752,16 +1828,22 @@ internal byte[] SerializeInt(int v, TdsParserStateObject stateObj) Debug.Assert(sizeof(int) == stateObj._bIntBytes.Length); } - WriteInt(stateObj._bIntBytes.AsSpan(), v); + BinaryPrimitives.WriteInt32LittleEndian(stateObj._bIntBytes, v); return stateObj._bIntBytes; } + /// + /// Writes a signed 32 bit integer to the wire. + /// + /// The value to write. + /// containing the wire buffer. internal void WriteInt(int v, TdsParserStateObject stateObj) { - Span buffer = stackalloc byte[sizeof(int)]; - WriteInt(buffer, v); if ((stateObj._outBytesUsed + 4) > stateObj._outBuff.Length) { + Span buffer = stackalloc byte[sizeof(int)]; + + BinaryPrimitives.WriteInt32LittleEndian(buffer, v); // if all of the int doesn't fit into the buffer for (int index = 0; index < sizeof(int); index++) { @@ -1771,19 +1853,16 @@ internal void WriteInt(int v, TdsParserStateObject stateObj) else { // all of the int fits into the buffer - buffer.CopyTo(stateObj._outBuff.AsSpan(stateObj._outBytesUsed, sizeof(int))); + BinaryPrimitives.WriteInt32LittleEndian(stateObj._outBuff.AsSpan(stateObj._outBytesUsed, sizeof(int)), v); stateObj._outBytesUsed += 4; } } - internal static void WriteInt(Span buffer, int value) - { - BinaryPrimitives.TryWriteInt32LittleEndian(buffer, value); - } - - // - // Takes a float and writes it as a 32 bit float. - // + /// + /// Serializes a float to the returned buffer. + /// + /// The value to serialize. + /// The serialized float. internal byte[] SerializeFloat(float v) { if (Single.IsInfinity(v) || Single.IsNaN(v)) @@ -1796,16 +1875,24 @@ internal byte[] SerializeFloat(float v) return bytes; } + /// + /// Writes a float to the wire. + /// + /// The value to write. + /// containing the wire buffer. internal void WriteFloat(float v, TdsParserStateObject stateObj) { Span bytes = stackalloc byte[sizeof(float)]; - FillFloatBytes(v, bytes); + BinaryPrimitives.WriteInt32LittleEndian(bytes, BitConverterCompatible.SingleToInt32Bits(v)); stateObj.WriteByteSpan(bytes); } - // - // Takes a long and writes it as a long. - // + /// + /// Serializes a signed 64 bit long to the returned buffer. + /// + /// The value to serialize. + /// containing the cached buffer bytes. + /// The serialized signed 64 bit long. internal byte[] SerializeLong(long v, TdsParserStateObject stateObj) { int current = 0; @@ -1829,6 +1916,11 @@ internal byte[] SerializeLong(long v, TdsParserStateObject stateObj) return bytes; } + /// + /// Writes a signed 64 bit long to the wire. + /// + /// The value to write. + /// containing the wire buffer. internal void WriteLong(long v, TdsParserStateObject stateObj) { if ((stateObj._outBytesUsed + 8) > stateObj._outBuff.Length) @@ -1855,9 +1947,12 @@ internal void WriteLong(long v, TdsParserStateObject stateObj) } } - // - // Takes a long and writes part of it - // + /// + /// Serializes the first bytes of a signed 64 bit long to the returned buffer. + /// + /// The value to serialize. + /// The number of bytes to serialize. + /// The serialized signed 64 bit long. internal byte[] SerializePartialLong(long v, int length) { Debug.Assert(length <= 8, "Length specified is longer than the size of a long"); @@ -1874,6 +1969,12 @@ internal byte[] SerializePartialLong(long v, int length) return bytes; } + /// + /// Writes the first bytes of a signed 64 bit long to the wire. + /// + /// The value to write. + /// The number of bytes to serialize. + /// containing the wire buffer. internal void WritePartialLong(long v, int length, TdsParserStateObject stateObj) { Debug.Assert(length <= 8, "Length specified is longer than the size of a long"); @@ -1898,17 +1999,21 @@ internal void WritePartialLong(long v, int length, TdsParserStateObject stateObj } } - // - // Takes a ulong and writes it as a ulong. - // + /// + /// Writes an unsigned 64 bit long to the wire. + /// + /// The value to write. + /// containing the wire buffer. internal void WriteUnsignedLong(ulong uv, TdsParserStateObject stateObj) { WriteLong((long)uv, stateObj); } - // - // Takes a double and writes it as a 64 bit double. - // + /// + /// Serializes a double to the returned buffer. + /// + /// The value to serialize. + /// The serialized double. internal byte[] SerializeDouble(double v) { if (double.IsInfinity(v) || double.IsNaN(v)) @@ -1921,10 +2026,15 @@ internal byte[] SerializeDouble(double v) return bytes; } + /// + /// Writes a double to the wire. + /// + /// The value to write. + /// containing the wire buffer. internal void WriteDouble(double v, TdsParserStateObject stateObj) { Span bytes = stackalloc byte[sizeof(double)]; - FillDoubleBytes(v, bytes); + BinaryPrimitives.WriteInt64LittleEndian(bytes, BitConverter.DoubleToInt64Bits(v)); stateObj.WriteByteSpan(bytes); } @@ -2071,7 +2181,7 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle #if DEBUG throw new InvalidOperationException(message); #else - throw SQL.ParsingError(); + throw SQL.ParsingErrorToken(ParsingErrorState.InvalidTdsTokenReceived, token); // MDAC 82443 #endif } @@ -2728,14 +2838,13 @@ private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateOb int processedLength = 0; SqlEnvChange head = null; SqlEnvChange tail = null; - TdsOperationStatus result; sqlEnvChange = null; while (tokenLength > processedLength) { SqlEnvChange env = new SqlEnvChange(); - result = stateObj.TryReadByte(out env._type); + TdsOperationStatus result = stateObj.TryReadByte(out env._type); if (result != TdsOperationStatus.Done) { return result; @@ -3641,7 +3750,7 @@ private TdsOperationStatus TryProcessSessionState(TdsParserStateObject stateObj, { if (length < 5) { - throw SQL.ParsingError(); + throw SQL.ParsingErrorLength(ParsingErrorState.SessionStateLengthTooShort, length); } uint seqNum; TdsOperationStatus result = stateObj.TryReadUInt32(out seqNum); @@ -3661,7 +3770,7 @@ private TdsOperationStatus TryProcessSessionState(TdsParserStateObject stateObj, } if (status > 1) { - throw SQL.ParsingError(); + throw SQL.ParsingErrorStatus(ParsingErrorState.SessionStateInvalidStatus, status); } bool recoverable = status != 0; length -= 5; @@ -4670,9 +4779,11 @@ internal void DrainData(TdsParserStateObject stateObj) if (sharedState != null && sharedState._dataReady) { _SqlMetaDataSet metadata = stateObj._cleanupMetaData; + TdsOperationStatus result; if (stateObj._partialHeaderBytesRead > 0) { - if (stateObj.TryProcessHeader() != TdsOperationStatus.Done) + result = stateObj.TryProcessHeader(); + if (result != TdsOperationStatus.Done) { throw SQL.SynchronousCallMayNotPend(); } @@ -4680,7 +4791,8 @@ internal void DrainData(TdsParserStateObject stateObj) if (0 == sharedState._nextColumnHeaderToRead) { // i. user called read but didn't fetch anything - if (stateObj.Parser.TrySkipRow(stateObj._cleanupMetaData, stateObj) != TdsOperationStatus.Done) + result = stateObj.Parser.TrySkipRow(stateObj._cleanupMetaData, stateObj); + if (result != TdsOperationStatus.Done) { throw SQL.SynchronousCallMayNotPend(); } @@ -4694,7 +4806,8 @@ internal void DrainData(TdsParserStateObject stateObj) { if (stateObj._longlen != 0) { - if (TrySkipPlpValue(ulong.MaxValue, stateObj, out _) != TdsOperationStatus.Done) + result = TrySkipPlpValue(ulong.MaxValue, stateObj, out _); + if (result != TdsOperationStatus.Done) { throw SQL.SynchronousCallMayNotPend(); } @@ -4703,7 +4816,8 @@ internal void DrainData(TdsParserStateObject stateObj) else if (0 < sharedState._columnDataBytesRemaining) { - if (stateObj.TrySkipLongBytes(sharedState._columnDataBytesRemaining) != TdsOperationStatus.Done) + result = stateObj.TrySkipLongBytes(sharedState._columnDataBytesRemaining); + if (result != TdsOperationStatus.Done) { throw SQL.SynchronousCallMayNotPend(); } @@ -4712,7 +4826,8 @@ internal void DrainData(TdsParserStateObject stateObj) // Read the remaining values off the wire for this row - if (stateObj.Parser.TrySkipRow(metadata, sharedState._nextColumnHeaderToRead, stateObj) != TdsOperationStatus.Done) + result = stateObj.Parser.TrySkipRow(metadata, sharedState._nextColumnHeaderToRead, stateObj); + if (result != TdsOperationStatus.Done) { throw SQL.SynchronousCallMayNotPend(); } @@ -5303,6 +5418,85 @@ private TdsOperationStatus TryCommonProcessMetaData(TdsParserStateObject stateOb return TdsOperationStatus.Done; } + private TdsOperationStatus TryProcessUDTMetaData(SqlMetaDataPriv metaData, TdsParserStateObject stateObj) + { + ushort shortLength; + byte byteLength; + + // max byte size + TdsOperationStatus result = stateObj.TryReadUInt16(out shortLength); + if (result != TdsOperationStatus.Done) + { + return result; + } + metaData.length = shortLength; + + // database name + result = stateObj.TryReadByte(out byteLength); + if (result != TdsOperationStatus.Done) + { + return result; + } + if (metaData.udt is null) + { + metaData.udt = new SqlMetaDataUdt(); + } + if (byteLength != 0) + { + result = stateObj.TryReadString(byteLength, out metaData.udt.DatabaseName); + if (result != TdsOperationStatus.Done) + { + return result; + } + } + + // schema name + result = stateObj.TryReadByte(out byteLength); + if (result != TdsOperationStatus.Done) + { + return result; + } + if (byteLength != 0) + { + result = stateObj.TryReadString(byteLength, out metaData.udt.SchemaName); + if (result != TdsOperationStatus.Done) + { + return result; + } + } + + // type name + result = stateObj.TryReadByte(out byteLength); + if (result != TdsOperationStatus.Done) + { + return result; + } + if (byteLength != 0) + { + result = stateObj.TryReadString(byteLength, out metaData.udt.TypeName); + if (result != TdsOperationStatus.Done) + { + return result; + } + } + + result = stateObj.TryReadUInt16(out shortLength); + if (result != TdsOperationStatus.Done) + { + return result; + } + if (shortLength != 0) + { + result = stateObj.TryReadString(shortLength, out metaData.udt.AssemblyQualifiedName); + if (result != TdsOperationStatus.Done) + { + return result; + } + } + + return TdsOperationStatus.Done; + } + private void WriteUDTMetaData(object value, string database, string schema, string type, TdsParserStateObject stateObj) { @@ -6536,9 +6730,8 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value, private TdsOperationStatus TryReadSqlDateTime(SqlBuffer value, byte tdsType, int length, byte scale, TdsParserStateObject stateObj) { Span datetimeBuffer = ((uint)length <= 16) ? stackalloc byte[16] : new byte[length]; - TdsOperationStatus result; - result = stateObj.TryReadByteArray(datetimeBuffer, length); + TdsOperationStatus result = stateObj.TryReadByteArray(datetimeBuffer, length); if (result != TdsOperationStatus.Done) { return result; @@ -6770,13 +6963,17 @@ internal TdsOperationStatus TryReadSqlValueInternal(SqlBuffer value, byte tdsTyp { Debug.Assert(length == GUID_SIZE, "invalid length for SqlGuid type!"); +#if NET Span b = stackalloc byte[GUID_SIZE]; +#else + byte[] b = (_tempGuidBytes ??= new byte[GUID_SIZE]); +#endif result = stateObj.TryReadByteArray(b, length); if (result != TdsOperationStatus.Done) { return result; } - value.Guid = ConstructGuid(b); + value.Guid = new Guid(b); break; } @@ -7122,12 +7319,8 @@ internal Task WriteSqlVariantValue(object value, int length, int offset, TdsPars case TdsEnums.SQLUNIQUEID: { - System.Guid guid = (System.Guid)value; - Span b = stackalloc byte[16]; - TdsParser.FillGuidBytes(guid, b); - - Debug.Assert((length == b.Length) && (length == 16), "Invalid length for guid type in com+ object"); - stateObj.WriteByteSpan(b); + Debug.Assert(length == 16, "Invalid length for guid type in com+ object"); + WriteGuid((Guid)value, stateObj); break; } @@ -7280,14 +7473,8 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta case TdsEnums.SQLUNIQUEID: { - System.Guid guid = (System.Guid)value; - Span b = stackalloc byte[16]; - FillGuidBytes(guid, b); - - length = b.Length; - Debug.Assert(length == 16, "Invalid length for guid type in com+ object"); WriteSqlVariantHeader(18, metatype.TDSType, metatype.PropBytes, stateObj); - stateObj.WriteByteSpan(b); + WriteGuid((Guid)value, stateObj); break; } @@ -8060,47 +8247,45 @@ internal TdsOperationStatus TryGetTokenLength(byte token, TdsParserStateObject s return stateObj.TryReadInt32(out tokenLength); } + if (token == TdsEnums.SQLUDT) + { // special case for UDTs + tokenLength = -1; // Should we return -1 or not call GetTokenLength for UDTs? + return TdsOperationStatus.Done; + } + else if (token == TdsEnums.SQLRETURNVALUE) { - if (token == TdsEnums.SQLUDT) - { // special case for UDTs - tokenLength = -1; // Should we return -1 or not call GetTokenLength for UDTs? - return TdsOperationStatus.Done; - } - else if (token == TdsEnums.SQLRETURNVALUE) - { - tokenLength = -1; // In 2005, the RETURNVALUE token stream no longer has length - return TdsOperationStatus.Done; - } - else if (token == TdsEnums.SQLXMLTYPE) - { - ushort value; - result = stateObj.TryReadUInt16(out value); - if (result != TdsOperationStatus.Done) - { - tokenLength = 0; - return result; - } - tokenLength = (int)value; - Debug.Assert(tokenLength == TdsEnums.SQL_USHORTVARMAXLEN, "Invalid token stream for xml datatype"); - return TdsOperationStatus.Done; - } - else if (token == TdsEnums.SQLJSON) + tokenLength = -1; // In 2005, the RETURNVALUE token stream no longer has length + return TdsOperationStatus.Done; + } + else if (token == TdsEnums.SQLXMLTYPE) + { + ushort value; + result = stateObj.TryReadUInt16(out value); + if (result != TdsOperationStatus.Done) { - tokenLength = -1; - return TdsOperationStatus.Done; + tokenLength = 0; + return result; } - else if (token == TdsEnums.SQLVECTOR) + tokenLength = (int)value; + Debug.Assert(tokenLength == TdsEnums.SQL_USHORTVARMAXLEN, "Invalid token stream for xml datatype"); + return TdsOperationStatus.Done; + } + else if (token == TdsEnums.SQLJSON) + { + tokenLength = -1; + return TdsOperationStatus.Done; + } + else if (token == TdsEnums.SQLVECTOR) + { + 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; - return TdsOperationStatus.Done; + tokenLength = 0; + return result; } + tokenLength = value; + return TdsOperationStatus.Done; } switch (token & TdsEnums.SQLLenMask) @@ -11664,26 +11849,16 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe case TdsEnums.SQLUNIQUEID: { Debug.Assert(actualLength == 16, "Invalid length for guid type in com+ object"); - Span b = stackalloc byte[16]; if (value is Guid guid) { - FillGuidBytes(guid, b); + WriteGuid(in guid, stateObj); } else { SqlGuid sqlGuid = (SqlGuid)value; - if (sqlGuid.IsNull) - { - b.Clear(); // this is needed because initlocals may be supressed in framework assemblies meaning the memory is not automaticaly zeroed - } - else - { - FillGuidBytes(sqlGuid.Value, b); - } + WriteGuid(in sqlGuid, stateObj); } - stateObj.WriteByteSpan(b); break; - } case TdsEnums.SQLBITN: @@ -12340,9 +12515,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int case TdsEnums.SQLUNIQUEID: { Debug.Assert(actualLength == 16, "Invalid length for guid type in com+ object"); - Span b = stackalloc byte[16]; - FillGuidBytes((System.Guid)value, b); - stateObj.WriteByteSpan(b); + WriteGuid((Guid)value, stateObj); break; } @@ -13541,86 +13714,6 @@ internal ulong PlpBytesTotalLength(TdsParserStateObject stateObj) return stateObj._longlen; } - private TdsOperationStatus TryProcessUDTMetaData(SqlMetaDataPriv metaData, TdsParserStateObject stateObj) - { - - ushort shortLength; - byte byteLength; - // max byte size - - TdsOperationStatus result = stateObj.TryReadUInt16(out shortLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - metaData.length = shortLength; - - // database name - result = stateObj.TryReadByte(out byteLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - if (metaData.udt is null) - { - metaData.udt = new SqlMetaDataUdt(); - } - if (byteLength != 0) - { - result = stateObj.TryReadString(byteLength, out metaData.udt.DatabaseName); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - - // schema name - result = stateObj.TryReadByte(out byteLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - if (byteLength != 0) - { - result = stateObj.TryReadString(byteLength, out metaData.udt.SchemaName); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - - // type name - result = stateObj.TryReadByte(out byteLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - if (byteLength != 0) - { - result = stateObj.TryReadString(byteLength, out metaData.udt.TypeName); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - - result = stateObj.TryReadUInt16(out shortLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - if (shortLength != 0) - { - result = stateObj.TryReadString(shortLength, out metaData.udt.AssemblyQualifiedName); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - - return TdsOperationStatus.Done; - } - const string StateTraceFormatString = "\n\t" + " _physicalStateObj = {0}\n\t" + " _pMarsPhysicalConObj = {1}\n\t" @@ -13645,8 +13738,9 @@ private TdsOperationStatus TryProcessUDTMetaData(SqlMetaDataPriv metaData, TdsPa + " _attentionWarnings = {20}\n\t" + " _statistics = {21}\n\t" + " _statisticsIsInTransaction = {22}\n\t" - + " _fPreserveTransaction = {23}" - + " _fParallel = {24}" + + " _fPreserveTransaction = {23}\n\t" + + " _multiSubnetFailover = {24}\n\t" + + " _transparentNetworkIPResolution = {25}" ; internal string TraceString() { @@ -13676,7 +13770,8 @@ internal string TraceString() _statistics == null ? bool.TrueString : bool.FalseString, _statisticsIsInTransaction ? bool.TrueString : bool.FalseString, _fPreserveTransaction ? bool.TrueString : bool.FalseString, - _connHandler == null ? "(null)" : _connHandler.ConnectionOptions.MultiSubnetFailover.ToString((IFormatProvider)null)); + _connHandler == null ? "(null)" : _connHandler.ConnectionOptions.MultiSubnetFailover.ToString((IFormatProvider)null), + _connHandler == null ? "(null)" : bool.FalseString); } private string TraceObjectClass(object instance) 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 deleted file mode 100644 index 95dd0d9731..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.netcore.cs +++ /dev/null @@ -1,25 +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.Buffers.Binary; - -namespace Microsoft.Data.SqlClient -{ - internal sealed partial class TdsParser - { - 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)); - - internal static void FillFloatBytes(float value, Span buffer) => BinaryPrimitives.TryWriteInt32LittleEndian(buffer, BitConverterCompatible.SingleToInt32Bits(value)); - - internal static Guid ConstructGuid(ReadOnlySpan bytes) - { - Debug.Assert(bytes.Length >= 16, "not enough bytes to set guid"); - return new Guid(bytes); - } - } -} 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 380a2aa746..f73b8682a0 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj @@ -49,5 +49,19 @@ + + + + + + + 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 17eeb3a472..a378ef5f51 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -1040,6 +1040,20 @@ + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs index 6e986435bc..d34b323951 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs @@ -110,24 +110,11 @@ protected override void AfterCleared(SqlCommand owner) } } - private int? _commandTimeout; - private UpdateRowSource _updatedRowSource = UpdateRowSource.Both; - private bool _designTimeInvisible; - /// /// Indicates if the column encryption setting was set at-least once in the batch rpc mode, when using AddBatchCommand. /// private bool _wasBatchModeColumnEncryptionSettingSetOnce; - /// - /// Column Encryption Override. Defaults to SqlConnectionSetting, in which case - /// it will be Enabled if SqlConnectionOptions.IsColumnEncryptionSettingEnabled = true, Disabled if false. - /// This may also be used to set other behavior which overrides connection level setting. - /// - private SqlCommandColumnEncryptionSetting _columnEncryptionSetting = SqlCommandColumnEncryptionSetting.UseConnectionSetting; - - internal SqlDependency _sqlDep; - #if DEBUG /// /// Force the client to sleep during sp_describe_parameter_encryption in the function TryFetchInputParameterEncryptionInfo. @@ -156,10 +143,6 @@ protected override void AfterCleared(SqlCommand owner) #endif internal static readonly Action s_cancelIgnoreFailure = CancelIgnoreFailureCallback; - private int _preparedConnectionCloseCount = -1; - private int _preparedConnectionReconnectCount = -1; - - private SqlParameterCollection _parameters; private _SqlRPC[] _rpcArrayOf1 = null; // Used for RPC executes private _SqlRPC _rpcForEncryption = null; // Used for sp_describe_parameter_encryption RPC executes @@ -191,23 +174,6 @@ private bool ShouldCacheEncryptionMetadata internal static int DebugForceAsyncWriteDelay { get; set; } #endif - /// - /// Return if column encryption setting is enabled. - /// The order in the below if is important since _activeConnection.Parser can throw if the - /// underlying tds connection is closed and we don't want to change the behavior for folks - /// not trying to use transparent parameter encryption i.e. who don't use (SqlCommandColumnEncryptionSetting.Enabled or _activeConnection.IsColumnEncryptionSettingEnabled) here. - /// - internal bool IsColumnEncryptionEnabled - { - get - { - return (_columnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled - || (_columnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && _activeConnection.IsColumnEncryptionSettingEnabled)) - && _activeConnection.Parser != null - && _activeConnection.Parser.IsColumnEncryptionSupported; - } - } - internal bool ShouldUseEnclaveBasedWorkflow => (!string.IsNullOrWhiteSpace(_activeConnection.EnclaveAttestationUrl) || Connection.AttestationProtocol == SqlConnectionAttestationProtocol.None) && IsColumnEncryptionEnabled; @@ -323,34 +289,13 @@ private AsyncState CachedAsyncState } } - // sql reader will pull this value out for each NextResult call. It is not cumulative - // _rowsAffected is cumulative for ExecuteNonQuery across all rpc batches - internal int _rowsAffected = -1; // rows affected by the command - // number of rows affected by sp_describe_parameter_encryption. // The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise. private int _rowsAffectedBySpDescribeParameterEncryption = -1; - - private SqlNotificationRequest _notification; - private bool _notificationAutoEnlist = true; // Notifications auto enlistment is turned on by default - - // transaction support - private SqlTransaction _transaction; - - private StatementCompletedEventHandler _statementCompletedEventHandler; - - // Volatile bool used to synchronize with cancel thread the state change of an executing - // command going from pre-processing to obtaining a stateObject. The cancel synchronization - // we require in the command is only from entering an Execute* API to obtaining a - // stateObj. Once a stateObj is successfully obtained, cancel synchronization is handled - // by the stateObject. - private volatile bool _pendingCancel; - - private bool _batchRPCMode; + private List<_SqlRPC> _RPCList; private _SqlRPC[] _sqlRPCParameterEncryptionReqArray; private int _currentlyExecutingBatch; - private SqlRetryLogicBaseProvider _retryLogicProvider; /// /// This variable is used to keep track of which RPC batch's results are being read when reading the results of @@ -428,254 +373,8 @@ private SqlCommand(SqlCommand from) : this() } } - /// - [DefaultValue(null)] - [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.DbCommand_Connection)] - new public SqlConnection Connection - { - get - { - return _activeConnection; - } - set - { - // Don't allow the connection to be changed while in an async operation. - if (_activeConnection != value && _activeConnection != null) - { - // If new value... - if (CachedAsyncState != null && CachedAsyncState.PendingAsyncOperation) - { - // If in pending async state, throw. - throw SQL.CannotModifyPropertyAsyncOperationInProgress(); - } - } - - // Check to see if the currently set transaction has completed. If so, - // null out our local reference. - if (_transaction != null && _transaction.Connection == null) - { - _transaction = null; - } - - // Command is no longer prepared on new connection, cleanup prepare status - if (IsPrepared) - { - if (_activeConnection != value && _activeConnection != null) - { - try - { - // cleanup - Unprepare(); - } - // @TODO: CER Exception Handling was removed here (see GH#3581) - catch (Exception) - { - // we do not really care about errors in unprepare (may be the old connection went bad) - } - finally - { - // clean prepare status (even successful Unprepare does not do that) - _prepareHandle = -1; - _execType = EXECTYPE.UNPREPARED; - } - } - } - _activeConnection = value; - SqlClientEventSource.Log.TryTraceEvent(" {0}, {1}", ObjectID, value?.ObjectID); - } - } - - /// - protected override DbConnection DbConnection - { - get - { - return Connection; - } - set - { - Connection = (SqlConnection)value; - } - } - - private SqlInternalConnectionTds InternalTdsConnection - { - get - { - return (SqlInternalConnectionTds)_activeConnection.InnerConnection; - } - } - private bool IsProviderRetriable => SqlConfigurableRetryFactory.IsRetriable(RetryLogicProvider); - /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public SqlRetryLogicBaseProvider RetryLogicProvider - { - get - { - if (_retryLogicProvider == null) - { - _retryLogicProvider = SqlConfigurableRetryLogicManager.CommandProvider; - } - return _retryLogicProvider; - } - set - { - _retryLogicProvider = value; - } - } - - /// - [DefaultValue(true)] - [ResCategory(StringsHelper.ResourceNames.DataCategory_Notification)] - [ResDescription(StringsHelper.ResourceNames.SqlCommand_NotificationAutoEnlist)] - public bool NotificationAutoEnlist - { - get - { - return _notificationAutoEnlist; - } - set - { - _notificationAutoEnlist = value; - } - } - - /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] // MDAC 90471 - [ResCategory(StringsHelper.ResourceNames.DataCategory_Notification)] - [ResDescription(StringsHelper.ResourceNames.SqlCommand_Notification)] - public SqlNotificationRequest Notification - { - get - { - return _notification; - } - set - { - SqlClientEventSource.Log.TryTraceEvent(" {0}", ObjectID); - _sqlDep = null; - _notification = value; - } - } - - internal SqlStatistics Statistics - { - get - { - if (_activeConnection != null) - { - if (_activeConnection.StatisticsEnabled) - { - return _activeConnection.Statistics; - } - } - return null; - } - } - - /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - [ResDescription(StringsHelper.ResourceNames.DbCommand_Transaction)] - new public SqlTransaction Transaction - { - get - { - // if the transaction object has been zombied, just return null - if (_transaction != null && _transaction.Connection == null) - { - _transaction = null; - } - return _transaction; - } - set - { - // Don't allow the transaction to be changed while in an async operation. - if (_transaction != value && _activeConnection != null) - { - // If new value... - if (CachedAsyncState.PendingAsyncOperation) - { - // If in pending async state, throw - throw SQL.CannotModifyPropertyAsyncOperationInProgress(); - } - } - _transaction = value; - SqlClientEventSource.Log.TryTraceEvent("SqlCommand.Set_Transaction | API | Object Id {0}, Internal Transaction Id {1}, Client Connection Id {2}", ObjectID, value?.InternalTransaction?.TransactionId, Connection?.ClientConnectionId); - } - } - - /// - protected override DbTransaction DbTransaction - { - get - { - return Transaction; - } - set - { - Transaction = (SqlTransaction)value; - SqlClientEventSource.Log.TryTraceEvent("SqlCommand.Set_DbTransaction | API | Object Id {0}, Client Connection Id {1}", ObjectID, Connection?.ClientConnectionId); - } - } - - /// - [DefaultValue("")] - [RefreshProperties(RefreshProperties.All)] // MDAC 67707 - [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.DbCommand_CommandText)] - public override string CommandText - { - get => _commandText ?? ""; - set - { - if (_commandText != value) - { - PropertyChanging(); - _commandText = value; - } - SqlClientEventSource.Log.TryTraceEvent("SqlCommand.Set_CommandText | API | Object Id {0}, String Value = '{1}', Client Connection Id {2}", ObjectID, value, Connection?.ClientConnectionId); - } - } - - /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.TCE_SqlCommand_ColumnEncryptionSetting)] - public SqlCommandColumnEncryptionSetting ColumnEncryptionSetting => _columnEncryptionSetting; - - /// - [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.DbCommand_CommandTimeout)] - public override int CommandTimeout - { - get - { - return _commandTimeout ?? DefaultCommandTimeout; - } - set - { - if (value < 0) - { - throw ADP.InvalidCommandTimeout(value, nameof(CommandTimeout)); - } - - if (value != _commandTimeout) - { - PropertyChanging(); - _commandTimeout = value; - } - - SqlClientEventSource.Log.TryTraceEvent("SqlCommand.Set_CommandTimeout | API | ObjectId {0}, Command Timeout value {1}, Client Connection Id {2}", ObjectID, value, Connection?.ClientConnectionId); - } - } - /// public void ResetCommandTimeout() { @@ -686,143 +385,6 @@ public void ResetCommandTimeout() } } - private int DefaultCommandTimeout - { - get - { - return _activeConnection?.CommandTimeout ?? ADP.DefaultCommandTimeout; - } - } - - /// - [DefaultValue(CommandType.Text)] - [RefreshProperties(RefreshProperties.All)] - [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.DbCommand_CommandType)] - public override CommandType CommandType - { - get - { - CommandType cmdType = _commandType; - return ((0 != cmdType) ? cmdType : CommandType.Text); - } - set - { - if (_commandType != value) - { - switch (value) - { - case CommandType.Text: - case CommandType.StoredProcedure: - PropertyChanging(); - _commandType = value; - break; - case System.Data.CommandType.TableDirect: - throw SQL.NotSupportedCommandType(value); - default: - throw ADP.InvalidCommandType(value); - } - - SqlClientEventSource.Log.TryTraceEvent("SqlCommand.Set_CommandType | API | ObjectId {0}, Command type value {1}, Client Connection Id {2}", ObjectID, (int)value, Connection?.ClientConnectionId); - } - } - } - - // By default, the cmd object is visible on the design surface (i.e. VS7 Server Tray) - // to limit the number of components that clutter the design surface, - // when the DataAdapter design wizard generates the insert/update/delete commands it will - // set the DesignTimeVisible property to false so that cmds won't appear as individual objects - /// - [DefaultValue(true)] - [DesignOnly(true)] - [Browsable(false)] - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool DesignTimeVisible - { - get - { - return !_designTimeInvisible; - } - set - { - _designTimeInvisible = !value; - TypeDescriptor.Refresh(this); - } - } - - /// - public bool EnableOptimizedParameterBinding { get; set; } - - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] - [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.DbCommand_Parameters)] - new public SqlParameterCollection Parameters - { - get - { - if (_parameters == null) - { - // delay the creation of the SqlParameterCollection - // until user actually uses the Parameters property - _parameters = new SqlParameterCollection(); - } - return _parameters; - } - } - - /// - protected override DbParameterCollection DbParameterCollection - { - get - { - return Parameters; - } - } - - /// - [DefaultValue(UpdateRowSource.Both)] - [ResCategory(StringsHelper.ResourceNames.DataCategory_Update)] - [ResDescription(StringsHelper.ResourceNames.DbCommand_UpdatedRowSource)] - public override UpdateRowSource UpdatedRowSource - { - get - { - return _updatedRowSource; - } - set - { - switch (value) - { - case UpdateRowSource.None: - case UpdateRowSource.OutputParameters: - case UpdateRowSource.FirstReturnedRecord: - case UpdateRowSource.Both: - _updatedRowSource = value; - break; - default: - throw ADP.InvalidUpdateRowSource(value); - } - - SqlClientEventSource.Log.TryTraceEvent("SqlCommand.UpdatedRowSource | API | ObjectId {0}, Updated row source value {1}, Client Connection Id {2}", ObjectID, (int)value, Connection?.ClientConnectionId); - } - } - - /// - [ResCategory(StringsHelper.ResourceNames.DataCategory_StatementCompleted)] - [ResDescription(StringsHelper.ResourceNames.DbCommand_StatementCompleted)] - public event StatementCompletedEventHandler StatementCompleted - { - add - { - _statementCompletedEventHandler += value; - } - remove - { - _statementCompletedEventHandler -= value; - } - } - internal void OnStatementCompleted(int recordCount) { if (0 <= recordCount) @@ -848,12 +410,6 @@ internal void OnStatementCompleted(int recordCount) } } - private void PropertyChanging() - { - // also called from SqlParameterCollection - this.IsDirty = true; - } - // Cancel is supposed to be multi-thread safe. // It doesn't make sense to verify the connection exists or that it is open during cancel // because immediately after checkin the connection can be closed or removed via another thread. @@ -6443,25 +5999,6 @@ internal int RowsAffectedByDescribeParameterEncryption } } - internal int InternalRecordsAffected - { - get - { - return _rowsAffected; - } - set - { - if (-1 == _rowsAffected) - { - _rowsAffected = value; - } - else if (0 < value) - { - _rowsAffected += value; - } - } - } - /// /// Clear the state in sqlcommand related to describe parameter encryption RPC requests. /// 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 d38190a359..670ed07dac 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 @@ -885,16 +885,13 @@ private void SendPreLoginHandshake( break; case (int)PreLoginOptions.TRACEID: - byte[] connectionIdBytes = _connHandler._clientConnectionId.ToByteArray(); - Debug.Assert(GUID_SIZE == connectionIdBytes.Length); - Buffer.BlockCopy(connectionIdBytes, 0, payload, payloadLength, GUID_SIZE); + SerializeGuid(_connHandler._clientConnectionId, payload.AsSpan(payloadLength, GUID_SIZE)); payloadLength += GUID_SIZE; offset += GUID_SIZE; optionDataSize = GUID_SIZE; ActivityCorrelator.ActivityId actId = ActivityCorrelator.Next(); - connectionIdBytes = actId.Id.ToByteArray(); - Buffer.BlockCopy(connectionIdBytes, 0, payload, payloadLength, GUID_SIZE); + SerializeGuid(actId.Id, payload.AsSpan(payloadLength, GUID_SIZE)); payloadLength += GUID_SIZE; payload[payloadLength++] = (byte)(0x000000ff & actId.Sequence); payload[payloadLength++] = (byte)((0x0000ff00 & actId.Sequence) >> 8); @@ -1085,7 +1082,7 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( payloadOffset = payload[offset++] << 8 | payload[offset++]; payloadLength = payload[offset++] << 8 | payload[offset++]; - EncryptionOptions serverOption = (EncryptionOptions)payload[payloadOffset]; + EncryptionOptions serverOption = ((EncryptionOptions)payload[payloadOffset]) & EncryptionOptions.OPTIONS_MASK; /* internal enum EncryptionOptions { OFF, @@ -1097,26 +1094,26 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( } */ // Any response other than NOT_SUP means the server supports encryption. - serverSupportsEncryption = (serverOption & EncryptionOptions.OPTIONS_MASK) != EncryptionOptions.NOT_SUP; + serverSupportsEncryption = serverOption != EncryptionOptions.NOT_SUP; - switch (_encryptionOption & EncryptionOptions.OPTIONS_MASK) + switch (_encryptionOption) { case (EncryptionOptions.OFF): - if ((serverOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.OFF) + if (serverOption == EncryptionOptions.OFF) { // Only encrypt login. - _encryptionOption = EncryptionOptions.LOGIN | (_encryptionOption & ~EncryptionOptions.OPTIONS_MASK); + _encryptionOption = EncryptionOptions.LOGIN; } - else if ((serverOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.REQ) + else if (serverOption == EncryptionOptions.REQ) { // Encrypt all. - _encryptionOption = EncryptionOptions.ON | (_encryptionOption & ~EncryptionOptions.OPTIONS_MASK); + _encryptionOption = EncryptionOptions.ON; } // NOT_SUP: No encryption. break; case (EncryptionOptions.NOT_SUP): - if ((serverOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.REQ) + if (serverOption == EncryptionOptions.REQ) { // Server requires encryption, but client does not support it. _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByClient(), "", 0)); @@ -1127,7 +1124,7 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( break; default: // Any other client option needs encryption - if ((serverOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.NOT_SUP) + if (serverOption == EncryptionOptions.NOT_SUP) { _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0)); _physicalStateObj.Dispose(); @@ -1216,8 +1213,8 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( } } - if ((_encryptionOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.ON || - (_encryptionOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.LOGIN) + if (_encryptionOption == EncryptionOptions.ON || + _encryptionOption == EncryptionOptions.LOGIN) { if (!serverSupportsEncryption) { @@ -1227,7 +1224,8 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( } // Validate Certificate if Trust Server Certificate=false and Encryption forced (EncryptionOptions.ON) from Server. - bool shouldValidateServerCert = (_encryptionOption == EncryptionOptions.ON && !trustServerCert) || ((_connHandler._accessTokenInBytes != null || _connHandler._accessTokenCallback != null) && !trustServerCert); + bool shouldValidateServerCert = (_encryptionOption == EncryptionOptions.ON && !trustServerCert) || + ((_connHandler._accessTokenInBytes != null || _connHandler._accessTokenCallback != null) && !trustServerCert); uint info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0) | TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE; @@ -1708,9 +1706,12 @@ internal void CheckResetConnection(TdsParserStateObject stateObj) #endif } - // - // Takes a 16 bit short and writes it to the returned buffer. - // + /// + /// Serializes a 16 bit short to the returned buffer. + /// + /// The value to serialize. + /// containing the cached buffer bytes. + /// The serialized 16 bit short. internal byte[] SerializeShort(int v, TdsParserStateObject stateObj) { if (stateObj._bShortBytes == null) @@ -1729,9 +1730,11 @@ internal byte[] SerializeShort(int v, TdsParserStateObject stateObj) return bytes; } - // - // Takes a 16 bit short and writes it. - // + /// + /// Writes a 16 bit short to the wire. + /// + /// The value to write. + /// containing the wire buffer. internal void WriteShort(int v, TdsParserStateObject stateObj) { if ((stateObj._outBytesUsed + 2) > stateObj._outBuff.Length) @@ -1749,27 +1752,97 @@ internal void WriteShort(int v, TdsParserStateObject stateObj) } } + /// + /// Writes a 16 bit unsigned short to the wire. + /// + /// The value to write. + /// containing the wire buffer. internal void WriteUnsignedShort(ushort us, TdsParserStateObject stateObj) { WriteShort((short)us, stateObj); } - // - // Takes a long and writes out an unsigned int - // + /// + /// Serializes a Guid to the specified buffer. + /// + /// The value to serialize. + /// The buffer to serialize to. The size of this buffer must be 16 bytes or larger. + private static void SerializeGuid(in Guid v, Span buffer) + { + Debug.Assert(buffer.Length >= GUID_SIZE); +#if NET + v.TryWriteBytes(buffer, bigEndian: false, out _); +#else + byte[] guidBytes = v.ToByteArray(); + guidBytes.AsSpan().CopyTo(buffer); +#endif + } + + /// + /// Writes a SqlGuid to the wire. + /// + /// The value to write. + /// containing the wire buffer. + private static void WriteGuid(in SqlGuid v, TdsParserStateObject stateObj) + { + Guid innerValue = v.IsNull ? Guid.Empty : v.Value; + + WriteGuid(in innerValue, stateObj); + } + + /// + /// Writes a Guid to the wire. + /// + /// The value to write. + /// containing the wire buffer. + private static void WriteGuid(in Guid v, TdsParserStateObject stateObj) + { + if ((stateObj._outBytesUsed + GUID_SIZE) > stateObj._outBuff.Length) + { + Span buffer = stackalloc byte[GUID_SIZE]; + + SerializeGuid(in v, buffer); + // if all of the guid doesn't fit into the buffer + for (int index = 0; index < buffer.Length; index++) + { + stateObj.WriteByte(buffer[index]); + } + } + else + { + // all of the guid fits into the buffer + SerializeGuid(in v, stateObj._outBuff.AsSpan(stateObj._outBytesUsed, GUID_SIZE)); + stateObj._outBytesUsed += GUID_SIZE; + } + } + + /// + /// Serializes an unsigned 32 bit integer to the returned buffer. + /// + /// The value to serialize. + /// containing the cached buffer bytes. + /// The serialized unsigned 32 bit integer. internal byte[] SerializeUnsignedInt(uint i, TdsParserStateObject stateObj) { return SerializeInt((int)i, stateObj); } + /// + /// Writes an unsigned 32 bit integer to the wire. + /// + /// The value to write. + /// containing the wire buffer. internal void WriteUnsignedInt(uint i, TdsParserStateObject stateObj) { WriteInt((int)i, stateObj); } - // - // Takes an int and writes it as an int. - // + /// + /// Serializes a signed 32 bit integer to the returned buffer. + /// + /// The value to serialize. + /// containing the cached buffer bytes. + /// The serialized signed 32 bit integer. internal byte[] SerializeInt(int v, TdsParserStateObject stateObj) { if (stateObj._bIntBytes == null) @@ -1781,40 +1854,41 @@ internal byte[] SerializeInt(int v, TdsParserStateObject stateObj) Debug.Assert(sizeof(int) == stateObj._bIntBytes.Length); } - int current = 0; - byte[] bytes = stateObj._bIntBytes; - bytes[current++] = (byte)(v & 0xff); - bytes[current++] = (byte)((v >> 8) & 0xff); - bytes[current++] = (byte)((v >> 16) & 0xff); - bytes[current++] = (byte)((v >> 24) & 0xff); - return bytes; + BinaryPrimitives.WriteInt32LittleEndian(stateObj._bIntBytes, v); + return stateObj._bIntBytes; } + /// + /// Writes a signed 32 bit integer to the wire. + /// + /// The value to write. + /// containing the wire buffer. internal void WriteInt(int v, TdsParserStateObject stateObj) { if ((stateObj._outBytesUsed + 4) > stateObj._outBuff.Length) { + Span buffer = stackalloc byte[sizeof(int)]; + + BinaryPrimitives.WriteInt32LittleEndian(buffer, v); // if all of the int doesn't fit into the buffer - for (int shiftValue = 0; shiftValue < sizeof(int) * 8; shiftValue += 8) + for (int index = 0; index < sizeof(int); index++) { - stateObj.WriteByte((byte)((v >> shiftValue) & 0xff)); + stateObj.WriteByte(buffer[index]); } } else { // all of the int fits into the buffer - // NOTE: We don't use a loop here for performance - stateObj._outBuff[stateObj._outBytesUsed] = (byte)(v & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 1] = (byte)((v >> 8) & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 2] = (byte)((v >> 16) & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 3] = (byte)((v >> 24) & 0xff); + BinaryPrimitives.WriteInt32LittleEndian(stateObj._outBuff.AsSpan(stateObj._outBytesUsed, sizeof(int)), v); stateObj._outBytesUsed += 4; } } - // - // Takes a float and writes it as a 32 bit float. - // + /// + /// Serializes a float to the returned buffer. + /// + /// The value to serialize. + /// The serialized float. internal byte[] SerializeFloat(float v) { if (Single.IsInfinity(v) || Single.IsNaN(v)) @@ -1825,16 +1899,24 @@ internal byte[] SerializeFloat(float v) return BitConverter.GetBytes(v); } + /// + /// Writes a float to the wire. + /// + /// The value to write. + /// containing the wire buffer. internal void WriteFloat(float v, TdsParserStateObject stateObj) { - byte[] bytes = BitConverter.GetBytes(v); - - stateObj.WriteByteArray(bytes, bytes.Length, 0); + Span bytes = stackalloc byte[sizeof(float)]; + BinaryPrimitives.WriteInt32LittleEndian(bytes, BitConverterCompatible.SingleToInt32Bits(v)); + stateObj.WriteByteSpan(bytes); } - // - // Takes a long and writes it as a long. - // + /// + /// Serializes a signed 64 bit long to the returned buffer. + /// + /// The value to serialize. + /// containing the cached buffer bytes. + /// The serialized signed 64 bit long. internal byte[] SerializeLong(long v, TdsParserStateObject stateObj) { int current = 0; @@ -1858,6 +1940,11 @@ internal byte[] SerializeLong(long v, TdsParserStateObject stateObj) return bytes; } + /// + /// Writes a signed 64 bit long to the wire. + /// + /// The value to write. + /// containing the wire buffer. internal void WriteLong(long v, TdsParserStateObject stateObj) { if ((stateObj._outBytesUsed + 8) > stateObj._outBuff.Length) @@ -1884,9 +1971,12 @@ internal void WriteLong(long v, TdsParserStateObject stateObj) } } - // - // Takes a long and writes part of it - // + /// + /// Serializes the first bytes of a signed 64 bit long to the returned buffer. + /// + /// The value to serialize. + /// The number of bytes to serialize. + /// The serialized signed 64 bit long. internal byte[] SerializePartialLong(long v, int length) { Debug.Assert(length <= 8, "Length specified is longer than the size of a long"); @@ -1903,6 +1993,12 @@ internal byte[] SerializePartialLong(long v, int length) return bytes; } + /// + /// Writes the first bytes of a signed 64 bit long to the wire. + /// + /// The value to write. + /// The number of bytes to serialize. + /// containing the wire buffer. internal void WritePartialLong(long v, int length, TdsParserStateObject stateObj) { Debug.Assert(length <= 8, "Length specified is longer than the size of a long"); @@ -1927,17 +2023,21 @@ internal void WritePartialLong(long v, int length, TdsParserStateObject stateObj } } - // - // Takes a ulong and writes it as a ulong. - // + /// + /// Writes an unsigned 64 bit long to the wire. + /// + /// The value to write. + /// containing the wire buffer. internal void WriteUnsignedLong(ulong uv, TdsParserStateObject stateObj) { WriteLong((long)uv, stateObj); } - // - // Takes a double and writes it as a 64 bit double. - // + /// + /// Serializes a double to the returned buffer. + /// + /// The value to serialize. + /// The serialized double. internal byte[] SerializeDouble(double v) { if (double.IsInfinity(v) || double.IsNaN(v)) @@ -1948,11 +2048,16 @@ internal byte[] SerializeDouble(double v) return BitConverter.GetBytes(v); } + /// + /// Writes a double to the wire. + /// + /// The value to write. + /// containing the wire buffer. internal void WriteDouble(double v, TdsParserStateObject stateObj) { - byte[] bytes = BitConverter.GetBytes(v); - - stateObj.WriteByteArray(bytes, bytes.Length, 0); + Span bytes = stackalloc byte[sizeof(double)]; + BinaryPrimitives.WriteInt64LittleEndian(bytes, BitConverter.DoubleToInt64Bits(v)); + stateObj.WriteByteSpan(bytes); } internal void PrepareResetConnection(bool preserveTransaction) @@ -4139,11 +4244,10 @@ internal TdsOperationStatus TryProcessReturnValue(int length, out SqlReturnValue returnValue, SqlCommandColumnEncryptionSetting columnEncryptionSetting) { - TdsOperationStatus result; returnValue = null; SqlReturnValue rec = new SqlReturnValue(); rec.length = length; // In 2005 this length is -1 - result = stateObj.TryReadUInt16(out rec.parmIndex); + TdsOperationStatus result = stateObj.TryReadUInt16(out rec.parmIndex); if (result != TdsOperationStatus.Done) { return result; @@ -4695,9 +4799,10 @@ internal void DrainData(TdsParserStateObject stateObj) if (sharedState != null && sharedState._dataReady) { var metadata = stateObj._cleanupMetaData; + TdsOperationStatus result; if (stateObj._partialHeaderBytesRead > 0) { - TdsOperationStatus result = stateObj.TryProcessHeader(); + result = stateObj.TryProcessHeader(); if (result != TdsOperationStatus.Done) { throw SQL.SynchronousCallMayNotPend(); @@ -4706,7 +4811,7 @@ internal void DrainData(TdsParserStateObject stateObj) if (0 == sharedState._nextColumnHeaderToRead) { // i. user called read but didn't fetch anything - TdsOperationStatus result = stateObj.Parser.TrySkipRow(stateObj._cleanupMetaData, stateObj); + result = stateObj.Parser.TrySkipRow(stateObj._cleanupMetaData, stateObj); if (result != TdsOperationStatus.Done) { throw SQL.SynchronousCallMayNotPend(); @@ -4721,7 +4826,7 @@ internal void DrainData(TdsParserStateObject stateObj) { if (stateObj._longlen != 0) { - TdsOperationStatus result = TrySkipPlpValue(UInt64.MaxValue, stateObj, out _); + result = TrySkipPlpValue(ulong.MaxValue, stateObj, out _); if (result != TdsOperationStatus.Done) { throw SQL.SynchronousCallMayNotPend(); @@ -4731,7 +4836,7 @@ internal void DrainData(TdsParserStateObject stateObj) else if (0 < sharedState._columnDataBytesRemaining) { - TdsOperationStatus result = stateObj.TrySkipLongBytes(sharedState._columnDataBytesRemaining); + result = stateObj.TrySkipLongBytes(sharedState._columnDataBytesRemaining); if (result != TdsOperationStatus.Done) { throw SQL.SynchronousCallMayNotPend(); @@ -4742,7 +4847,8 @@ internal void DrainData(TdsParserStateObject stateObj) // Read the remaining values off the wire for this row - if (stateObj.Parser.TrySkipRow(metadata, sharedState._nextColumnHeaderToRead, stateObj) != TdsOperationStatus.Done) + result = stateObj.Parser.TrySkipRow(metadata, sharedState._nextColumnHeaderToRead, stateObj); + if (result != TdsOperationStatus.Done) { throw SQL.SynchronousCallMayNotPend(); } @@ -5069,9 +5175,10 @@ internal TdsOperationStatus TryProcessMetaData(int cColumns, TdsParserStateObjec // Read the cipher info table first SqlTceCipherInfoTable cipherTable = null; + TdsOperationStatus result; if (IsColumnEncryptionSupported) { - TdsOperationStatus result = TryProcessCipherInfoTable(stateObj, out cipherTable); + result = TryProcessCipherInfoTable(stateObj, out cipherTable); if (result != TdsOperationStatus.Done) { metaData = null; @@ -5083,7 +5190,7 @@ internal TdsOperationStatus TryProcessMetaData(int cColumns, TdsParserStateObjec _SqlMetaDataSet newMetaData = new _SqlMetaDataSet(cColumns, cipherTable); for (int i = 0; i < cColumns; i++) { - TdsOperationStatus result = TryCommonProcessMetaData(stateObj, newMetaData[i], cipherTable, fColMD: true, columnEncryptionSetting: columnEncryptionSetting); + result = TryCommonProcessMetaData(stateObj, newMetaData[i], cipherTable, fColMD: true, columnEncryptionSetting: columnEncryptionSetting); if (result != TdsOperationStatus.Done) { metaData = null; @@ -5302,10 +5409,9 @@ private TdsOperationStatus TryCommonProcessMetaData(TdsParserStateObject stateOb { byte byteLen; uint userType; - TdsOperationStatus result; // read user type - 4 bytes 2005, 2 backwards - result = stateObj.TryReadUInt32(out userType); + TdsOperationStatus result = stateObj.TryReadUInt32(out userType); if (result != TdsOperationStatus.Done) { return result; @@ -5637,13 +5743,12 @@ private TdsOperationStatus TryProcessColInfo(_SqlMetaDataSet columns, SqlDataRea Debug.Assert(columns != null && columns.Length > 0, "no metadata available!"); metaData = null; - TdsOperationStatus result; for (int i = 0; i < columns.Length; i++) { _SqlMetaData col = columns[i]; - result = stateObj.TryReadByte(out _); + TdsOperationStatus result = stateObj.TryReadByte(out _); if (result != TdsOperationStatus.Done) { return result; @@ -6932,18 +7037,17 @@ internal TdsOperationStatus TryReadSqlValueInternal(SqlBuffer value, byte tdsTyp { Debug.Assert(length == GUID_SIZE, "invalid length for SqlGuid type!"); - byte[] b = _tempGuidBytes; - if (b is null) - { - b = new byte[GUID_SIZE]; - } +#if NET + Span b = stackalloc byte[GUID_SIZE]; +#else + byte[] b = (_tempGuidBytes ??= new byte[GUID_SIZE]); +#endif result = stateObj.TryReadByteArray(b, length); if (result != TdsOperationStatus.Done) { return result; } value.Guid = new Guid(b); - _tempGuidBytes = b; break; } @@ -7289,11 +7393,8 @@ internal Task WriteSqlVariantValue(object value, int length, int offset, TdsPars case TdsEnums.SQLUNIQUEID: { - System.Guid guid = (System.Guid)value; - byte[] b = guid.ToByteArray(); - - Debug.Assert((length == b.Length) && (length == 16), "Invalid length for guid type in com+ object"); - stateObj.WriteByteArray(b, length, 0); + Debug.Assert(length == 16, "Invalid length for guid type in com+ object"); + WriteGuid((Guid)value, stateObj); break; } @@ -7446,13 +7547,8 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta case TdsEnums.SQLUNIQUEID: { - System.Guid guid = (System.Guid)value; - byte[] b = guid.ToByteArray(); - - length = b.Length; - Debug.Assert(length == 16, "Invalid length for guid type in com+ object"); WriteSqlVariantHeader(18, metatype.TDSType, metatype.PropBytes, stateObj); - stateObj.WriteByteArray(b, length, 0); + WriteGuid((Guid)value, stateObj); break; } @@ -8208,6 +8304,7 @@ internal TdsOperationStatus TryGetDataLength(SqlMetaDataPriv colmeta, TdsParserS internal TdsOperationStatus TryGetTokenLength(byte token, TdsParserStateObject stateObj, out int tokenLength) { Debug.Assert(token != 0, "0 length token!"); + TdsOperationStatus result; switch (token) { // rules about SQLLenMask no longer apply to new tokens (as of 7.4) @@ -8219,8 +8316,6 @@ internal TdsOperationStatus TryGetTokenLength(byte token, TdsParserStateObject s return stateObj.TryReadInt32(out tokenLength); } - TdsOperationStatus result; - if (token == TdsEnums.SQLUDT) { // special case for UDTs tokenLength = -1; // Should we return -1 or not call GetTokenLength for UDTs? @@ -8590,63 +8685,63 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD return totalLen; } - internal int WriteDataClassificationFeatureRequest(bool write /* if false just calculates the length */) + internal int WriteTceFeatureRequest(bool write /* if false just calculates the length */) { int len = 6; // 1byte = featureID, 4bytes = featureData length, 1 bytes = Version if (write) { - // Write Feature ID, length of the version# field and Sensitivity Classification Version# - _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_DATACLASSIFICATION); + // Write Feature ID, length of the version# field and TCE Version# + _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_TCE); WriteInt(1, _physicalStateObj); - _physicalStateObj.WriteByte(TdsEnums.DATA_CLASSIFICATION_VERSION_MAX_SUPPORTED); + _physicalStateObj.WriteByte(TdsEnums.MAX_SUPPORTED_TCE_VERSION); } return len; // size of data written } - internal int WriteTceFeatureRequest(bool write /* if false just calculates the length */) + internal int WriteAzureSQLSupportFeatureRequest(bool write /* if false just calculates the length */) { - int len = 6; // 1byte = featureID, 4bytes = featureData length, 1 bytes = Version + int len = 6; // 1byte = featureID, 4bytes = featureData length, 1 bytes = featureData if (write) { - // Write Feature ID, length of the version# field and TCE Version# - _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_TCE); - WriteInt(1, _physicalStateObj); - _physicalStateObj.WriteByte(TdsEnums.MAX_SUPPORTED_TCE_VERSION); + // Write Feature ID + _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_AZURESQLSUPPORT); + + // Feature Data length + WriteInt(s_featureExtDataAzureSQLSupportFeatureRequest.Length, _physicalStateObj); + + _physicalStateObj.WriteByteArray(s_featureExtDataAzureSQLSupportFeatureRequest, s_featureExtDataAzureSQLSupportFeatureRequest.Length, 0); } - return len; // size of data written + return len; } - internal int WriteGlobalTransactionsFeatureRequest(bool write /* if false just calculates the length */) + internal int WriteDataClassificationFeatureRequest(bool write /* if false just calculates the length */) { - int len = 5; // 1byte = featureID, 4bytes = featureData length + int len = 6; // 1byte = featureID, 4bytes = featureData length, 1 bytes = Version if (write) { - // Write Feature ID - _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_GLOBALTRANSACTIONS); - WriteInt(0, _physicalStateObj); // we don't send any data + // Write Feature ID, length of the version# field and Sensitivity Classification Version# + _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_DATACLASSIFICATION); + WriteInt(1, _physicalStateObj); + _physicalStateObj.WriteByte(TdsEnums.DATA_CLASSIFICATION_VERSION_MAX_SUPPORTED); } - return len; + return len; // size of data written } - internal int WriteAzureSQLSupportFeatureRequest(bool write /* if false just calculates the length */) + internal int WriteGlobalTransactionsFeatureRequest(bool write /* if false just calculates the length */) { - int len = 6; // 1byte = featureID, 4bytes = featureData length, 1 bytes = featureData + int len = 5; // 1byte = featureID, 4bytes = featureData length if (write) { // Write Feature ID - _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_AZURESQLSUPPORT); - - // Feature Data length - WriteInt(s_featureExtDataAzureSQLSupportFeatureRequest.Length, _physicalStateObj); - - _physicalStateObj.WriteByteArray(s_featureExtDataAzureSQLSupportFeatureRequest, s_featureExtDataAzureSQLSupportFeatureRequest.Length, 0); + _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_GLOBALTRANSACTIONS); + WriteInt(0, _physicalStateObj); // we don't send any data } return len; @@ -9533,29 +9628,34 @@ internal Task TdsExecuteSQLBatch(string text, int timeout, SqlNotificationReques // Need to wait for flush - continuation will unlock the connection bool taskReleaseConnectionLock = releaseConnectionLock; releaseConnectionLock = false; - return executeTask.ContinueWith(t => - { - Debug.Assert(!t.IsCanceled, "Task should not be canceled"); - try - { - if (t.IsFaulted) - { - FailureCleanup(stateObj, t.Exception.InnerException); - throw t.Exception.InnerException; - } - else + return executeTask.ContinueWith( + static (Task task, object state) => + { + Debug.Assert(!task.IsCanceled, "Task should not be canceled"); + var parameters = (Tuple)state; + TdsParser parser = parameters.Item1; + TdsParserStateObject tdsParserStateObject = parameters.Item2; + SqlInternalConnectionTds internalConnectionTds = parameters.Item3; + try { - stateObj.SniContext = SniContext.Snix_Read; + if (task.IsFaulted) + { + parser.FailureCleanup(tdsParserStateObject, task.Exception.InnerException); + throw task.Exception.InnerException; + } + else + { + tdsParserStateObject.SniContext = SniContext.Snix_Read; + } } - } - finally - { - if (taskReleaseConnectionLock) + finally { - _connHandler._parserLock.Release(); + internalConnectionTds?._parserLock.Release(); } - } - }, TaskScheduler.Default); + }, + Tuple.Create(this, stateObj, taskReleaseConnectionLock ? _connHandler : null), + TaskScheduler.Default + ); } // Finished sync @@ -10329,27 +10429,23 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet // This is in its own method to avoid always allocating the lambda in TDSExecuteRPCParameter private void TDSExecuteRPCParameterSetupWriteCompletion(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout, bool inSchema, SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj, bool isCommandProc, bool sync, TaskCompletionSource completion, int startRpc, int startParam, Task writeParamTask) { - AsyncHelper.ContinueTaskWithState( + AsyncHelper.ContinueTask( writeParamTask, completion, - this, - (object state) => - { - TdsParser tdsParser = (TdsParser)state; - TdsExecuteRPC( - cmd, - rpcArray, - timeout, - inSchema, - notificationRequest, - stateObj, - isCommandProc, - sync, - completion, - startRpc, - startParam); - }, - onFailure: (Exception exc, object state) => ((TdsParser)state).TdsExecuteRPC_OnFailure(exc, stateObj), + () => TdsExecuteRPC( + cmd, + rpcArray, + timeout, + inSchema, + notificationRequest, + stateObj, + isCommandProc, + sync, + completion, + startRpc, + startParam + ), + onFailure: exc => TdsExecuteRPC_OnFailure(exc, stateObj), connectionToDoom: _connHandler ); } @@ -11110,7 +11206,7 @@ internal void WriteBulkCopyMetaData(_SqlMetaDataSet metadataCollection, int coun stateObj.WriteByte(md.scale); break; case SqlDbTypeExtensions.Json: - stateObj.WriteByteArray(s_jsonMetadataSubstituteSequence, s_xmlMetadataSubstituteSequence.Length, 0); + stateObj.WriteByteArray(s_jsonMetadataSubstituteSequence, s_jsonMetadataSubstituteSequence.Length, 0); break; case SqlDbTypeExtensions.Vector: stateObj.WriteByte(md.tdsType); @@ -11834,18 +11930,16 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe case TdsEnums.SQLUNIQUEID: { - byte[] b; + Debug.Assert(actualLength == 16, "Invalid length for guid type in com+ object"); if (value is Guid guid) { - b = guid.ToByteArray(); + WriteGuid(in guid, stateObj); } else { - b = ((SqlGuid)value).ToByteArray(); + SqlGuid sqlGuid = (SqlGuid)value; + WriteGuid(in sqlGuid, stateObj); } - - Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object"); - stateObj.WriteByteArray(b, actualLength, 0); break; } @@ -12501,11 +12595,8 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int case TdsEnums.SQLUNIQUEID: { - System.Guid guid = (System.Guid)value; - byte[] b = guid.ToByteArray(); - - Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object"); - stateObj.WriteByteArray(b, actualLength, 0); + Debug.Assert(actualLength == 16, "Invalid length for guid type in com+ object"); + WriteGuid((Guid)value, stateObj); break; } @@ -13218,6 +13309,8 @@ internal TdsOperationStatus TryReadPlpUnicodeCharsWithContinue(TdsParserStateObj char[] temp = null; bool buffIsRented = false; int startOffset = 0; + + stateObj.RequestContinue(true); (bool canContinue, bool isStarting, bool isContinuing) = stateObj.GetSnapshotStatuses(); if (canContinue) @@ -13626,8 +13719,8 @@ internal TdsOperationStatus TrySkipPlpValue(ulong cb, TdsParserStateObject state { // Read and skip cb bytes or until ReadPlpLength returns 0. int bytesSkipped; - totalBytesSkipped = 0; TdsOperationStatus result; + totalBytesSkipped = 0; if (stateObj._longlenleft == 0) { @@ -13727,8 +13820,9 @@ internal ulong PlpBytesTotalLength(TdsParserStateObject stateObj) + " _attentionWarnings = {20}\n\t" + " _statistics = {21}\n\t" + " _statisticsIsInTransaction = {22}\n\t" - + " _fPreserveTransaction = {23}" - + " _fParallel = {24}" + + " _fPreserveTransaction = {23}\n\t" + + " _multiSubnetFailover = {24}\n\t" + + " _transparentNetworkIPResolution = {25}" ; internal string TraceString() { 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 41f2c571ff..248fe0f985 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -629,6 +629,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); @@ -1166,6 +1173,9 @@ internal static Exception DeriveParametersNotSupported(IDbCommand value) => DataAdapter(StringsHelper.GetString(Strings.ADP_DeriveParametersNotSupported, value.GetType().Name, value.CommandType.ToString())); internal static Exception NoStoredProcedureExists(string sproc) => InvalidOperation(StringsHelper.GetString(Strings.ADP_NoStoredProcedureExists, sproc)); + + internal static Exception InvalidCommandTimeout(int value, [CallerMemberName] string property = "") + => Argument(StringsHelper.GetString(Strings.ADP_InvalidCommandTimeout, value.ToString(CultureInfo.InvariantCulture)), property); #endregion #region DbMetaDataFactory @@ -1496,14 +1506,6 @@ internal static Exception NumericToDecimalOverflow() return InvalidCast(StringsHelper.GetString(Strings.ADP_NumericToDecimalOverflow)); } - // - // IDbCommand - // - internal static Exception InvalidCommandTimeout(int value, string name) - { - return Argument(StringsHelper.GetString(Strings.ADP_InvalidCommandTimeout, value.ToString(CultureInfo.InvariantCulture)), name); - } - // // DbDataAdapter // @@ -1633,12 +1635,6 @@ internal static ArgumentOutOfRangeException InvalidParameterDirection(ParameterD #endif return InvalidEnumerationValue(typeof(ParameterDirection), (int)value); } - - // - // IDbCommand - // - internal static Exception InvalidCommandTimeout(int value, [CallerMemberName] string property = "") - => Argument(StringsHelper.GetString(Strings.ADP_InvalidCommandTimeout, value.ToString(CultureInfo.InvariantCulture)), property); #endregion #endif } 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 e1a86fa0e7..472edf6888 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -520,8 +520,19 @@ internal void DeactivateConnection() SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Deactivating", ObjectID); #if DEBUG - int activateCount = Interlocked.Decrement(ref _activateCount); - Debug.Assert(activateCount == 0, "activated multiple times?"); + int origCount, newCount; + do + { + origCount = _activateCount; + + if (origCount == 0) + { + break; + } + + newCount = origCount - 1; + } + while (Interlocked.CompareExchange(ref _activateCount, newCount, origCount) != origCount); #endif SqlClientEventSource.Metrics.ExitActiveConnection(); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs index 67865bb155..8b96a7b421 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -9,6 +9,11 @@ using System.Diagnostics; using System.Threading; using Microsoft.Data.Common; +using Microsoft.Data.Sql; + +#if NET +using Microsoft.Data.SqlClient.Diagnostics; +#endif namespace Microsoft.Data.SqlClient { @@ -20,12 +25,21 @@ public sealed partial class SqlCommand : DbCommand, ICloneable { #region Constants + /// + /// Pre-boxed invalid prepare handle - used to optimize boxing behavior. + /// private static readonly object s_cachedInvalidPrepareHandle = (object)-1; #endregion #region Fields + + // @TODO: Make property - non-private fields are bad + internal SqlDependency _sqlDep; + // @TODO: Rename _batchRpcMode to follow pattern + private bool _batchRPCMode; + /// /// Number of instances of SqlCommand that have been created. Used to generate ObjectId /// @@ -36,15 +50,39 @@ public sealed partial class SqlCommand : DbCommand, ICloneable /// private SqlConnection _activeConnection; + /// + /// Column Encryption Override. Defaults to SqlConnectionSetting, in which case it will be + /// Enabled if SqlConnectionOptions.IsColumnEncryptionSettingEnabled = true, Disabled if + /// false. This may also be used to set other behavior which overrides connection level + /// setting. + /// + // @TODO: Make auto-property + private SqlCommandColumnEncryptionSetting _columnEncryptionSetting = + SqlCommandColumnEncryptionSetting.UseConnectionSetting; + /// /// Text to execute when executing the command. /// private string _commandText; + /// + /// Maximum amount of time, in seconds, the command will execute before timing out. + /// + private int? _commandTimeout; + /// /// Type of the command to execute. /// private CommandType _commandType; + + /// + /// By default, the cmd object is visible on the design surface (i.e. VS7 Server Tray) to + /// limit the number of components that clutter the design surface, when the DataAdapter + /// design wizard generates the insert/update/delete commands it will set the + /// DesignTimeVisible property to false so that cmds won't appear as individual objects + /// + // @TODO: Make auto-property + private bool _designTimeInvisible; /// /// Current state of preparation of the command. @@ -73,21 +111,102 @@ public sealed partial class SqlCommand : DbCommand, ICloneable /// // @TODO: Make auto-property private bool _inPrepare = false; + + private SqlNotificationRequest _notification; + + #if NETFRAMEWORK + // @TODO: Make auto-property + private bool _notificationAutoEnlist = true; + #endif + + /// + /// Parameters that have been added to the current instance. + /// + private SqlParameterCollection _parameters; + + /// + /// Volatile bool used to synchronize with cancel thread the state change of an executing + /// command going from pre-processing to obtaining a stateObject. The cancel + /// synchronization we require in the command is only from entering an Execute* API to + /// obtaining a stateObj. Once a stateObj is successfully obtained, cancel synchronization + /// is handled by the stateObject. + /// + private volatile bool _pendingCancel; + + /// + /// Number of times the connection was closed when the command was prepared. Used to + /// determine if the connection has closed between prepare and execute. + /// + private int _preparedConnectionCloseCount = -1; /// - /// The handle of a prepared command. Apparently there can be multiple prepared commands at - /// a time - a feature that we do not support yet. this is an int which is used in the + /// Number of times the connection was reconnected when the command was prepared. Used to + /// determine if the connection has reconnected between prepare and execute. + /// + private int _preparedConnectionReconnectCount = -1; + + /// + /// the handle of a prepared command. Apparently there can be multiple prepared commands at + /// a time - a feature that we do not support yet. This is an int which is used in the /// object typed SqlParameter.Value field, avoid repeated boxing by storing in a box. /// - private object _prepareHandle = s_cachedInvalidPrepareHandle; // this is an int which is used in the object typed SqlParameter.Value field, avoid repeated boxing by storing in a box + private object _prepareHandle = s_cachedInvalidPrepareHandle; + + /// + /// Retry logic provider to use for execution of the current instance. + /// + private SqlRetryLogicBaseProvider _retryLogicProvider; + + /// + /// Sql reader will pull this value out for each NextResult call. It is not cumulative. + /// _rowsAffected is cumulative for ExecuteNonQuery across all rpc batches + /// + // @TODO: Use int? and replace -1 usage with null + private int _rowsAffected = -1; /// /// TDS session the current instance is using. /// private TdsParserStateObject _stateObj; + + /// + /// Event to call when a statement completes. + /// + // @TODO: Make auto-event? + private StatementCompletedEventHandler _statementCompletedEventHandler; + + /// + /// Current transaction the command is participating in. + /// + private SqlTransaction _transaction; + + /// + /// How command results are applied to a DataRow when used by the update method of + /// DbDataAdapter. + /// + private UpdateRowSource _updatedRowSource = UpdateRowSource.Both; #endregion + #region Events + + /// + [ResCategory(StringsHelper.ResourceNames.DataCategory_StatementCompleted)] + [ResDescription(StringsHelper.ResourceNames.DbCommand_StatementCompleted)] + public event StatementCompletedEventHandler StatementCompleted + { + add + { + _statementCompletedEventHandler += value; + } + remove + { + _statementCompletedEventHandler -= value; + } + } + + #endregion + #region Enums // @TODO: Rename to match naming conventions @@ -111,13 +230,417 @@ private enum EXECTYPE #endregion - #region Properties + #region Public Properties + + /// + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] + [ResDescription(StringsHelper.ResourceNames.TCE_SqlCommand_ColumnEncryptionSetting)] + public SqlCommandColumnEncryptionSetting ColumnEncryptionSetting => _columnEncryptionSetting; - internal bool InPrepare => _inPrepare; + /// + [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] + [ResDescription(StringsHelper.ResourceNames.DbCommand_CommandTimeout)] + public override int CommandTimeout + { + get => _commandTimeout ?? DefaultCommandTimeout; + set + { + if (value < 0) + { + throw ADP.InvalidCommandTimeout(value); + } + + if (value != _commandTimeout) + { + PropertyChanging(); + _commandTimeout = value; + } + + SqlClientEventSource.Log.TryTraceEvent( + "SqlCommand.Set_CommandTimeout | API | " + + $"Object Id {ObjectID}, " + + $"Command Timeout value {value}, " + + $"Client Connection Id {Connection?.ClientConnectionId}"); + } + } + + /// + [DefaultValue("")] + [RefreshProperties(RefreshProperties.All)] + [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] + [ResDescription(StringsHelper.ResourceNames.DbCommand_CommandText)] + public override string CommandText + { + get => _commandText ?? string.Empty; + set + { + if (_commandText != value) + { + PropertyChanging(); + _commandText = value; + } + + SqlClientEventSource.Log.TryTraceEvent( + "SqlCommand.Set_CommandText | API | " + + $"Object Id {ObjectID}, " + + $"String Value = '{value}', " + + $"Client Connection Id {Connection?.ClientConnectionId}"); + } + } + + /// + [DefaultValue(CommandType.Text)] + [RefreshProperties(RefreshProperties.All)] + [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] + [ResDescription(StringsHelper.ResourceNames.DbCommand_CommandType)] + public override CommandType CommandType + { + get => _commandType != 0 ? _commandType : CommandType.Text; + set + { + if (_commandType != value) + { + switch (value) + { + case CommandType.Text: + case CommandType.StoredProcedure: + PropertyChanging(); + _commandType = value; + break; + case CommandType.TableDirect: + throw SQL.NotSupportedCommandType(value); + default: + throw ADP.InvalidCommandType(value); + } + + // @TODO: Either move this outside the if block or move all the other instances inside the if block. + SqlClientEventSource.Log.TryTraceEvent( + "SqlCommand.Set_CommandType | API | " + + $"Object Id {ObjectID}, " + + $"Command Type value {(int)value}, " + + $"Client Connection Id {Connection?.ClientConnectionId}"); + } + } + } + + /// + [DefaultValue(null)] + [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] + [ResDescription(StringsHelper.ResourceNames.DbCommand_Connection)] + public new SqlConnection Connection + { + get => _activeConnection; + set + { + // Don't allow the connection to be changed while in an async operation + // @TODO: Factor out + if (_activeConnection != value && _activeConnection != null) + { + // If new value... + if (CachedAsyncState != null && CachedAsyncState.PendingAsyncOperation) + { + // If in pending async state, throw. + throw SQL.CannotModifyPropertyAsyncOperationInProgress(); + } + } + + // Check to see if the currently set transaction has completed. If so, null out + // our local reference. + if (_transaction?.Connection is null) + { + _transaction = null; + } + + if (IsPrepared) + { + if (_activeConnection != value && _activeConnection != null) + { + try + { + Unprepare(); + } + // @TODO: CER Exception Handling was removed here (see GH#3581) + catch (Exception) + { + // We do not really care about errors in unprepare (maybe the old + // connection went bad?) + } + finally + { + // Clean prepare status (even successful unprepare does not do that) + // @TODO: ... but it does? + _prepareHandle = s_cachedInvalidPrepareHandle; + _execType = EXECTYPE.UNPREPARED; + } + } + } + + _activeConnection = value; + + SqlClientEventSource.Log.TryTraceEvent( + "SqlCommand.Set_Connection | API | " + + $"Object Id {ObjectID}, " + + $"Client Connection Id {value?.ClientConnectionId}"); + } + } + + /// + [DefaultValue(true)] + [DesignOnly(true)] + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool DesignTimeVisible + { + get => !_designTimeInvisible; + set + { + _designTimeInvisible = !value; + + #if NETFRAMEWORK + TypeDescriptor.Refresh(this); + #endif + } + } + + /// + public bool EnableOptimizedParameterBinding { get; set; } + + /// + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [ResCategory(StringsHelper.ResourceNames.DataCategory_Notification)] + [ResDescription(StringsHelper.ResourceNames.SqlCommand_Notification)] + public SqlNotificationRequest Notification + { + get => _notification; + set + { + _sqlDep = null; + _notification = value; + SqlClientEventSource.Log.TryTraceEvent( + "SqlCommand.Set_Notification | API | " + + $"Object Id {ObjectID}"); + } + } + + #if NETFRAMEWORK + /// + [DefaultValue(true)] + [ResCategory(StringsHelper.ResourceNames.DataCategory_Notification)] + [ResDescription(StringsHelper.ResourceNames.SqlCommand_NotificationAutoEnlist)] + public bool NotificationAutoEnlist + { + get => _notificationAutoEnlist; + set => _notificationAutoEnlist = value; + } + #endif + + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] + [ResDescription(StringsHelper.ResourceNames.DbCommand_Parameters)] + public new SqlParameterCollection Parameters + { + get + { + // Delay the creation of the SqlParameterCollection until user actually uses the + // Parameters property. + _parameters ??= new SqlParameterCollection(); + return _parameters; + } + } + + /// + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public SqlRetryLogicBaseProvider RetryLogicProvider + { + get + { + _retryLogicProvider ??= SqlConfigurableRetryLogicManager.CommandProvider; + return _retryLogicProvider; + } + set => _retryLogicProvider = value; + } + + /// + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [ResDescription(StringsHelper.ResourceNames.DbCommand_Transaction)] + public new SqlTransaction Transaction + { + get + { + // If the transaction object has been zombied, just return null + if (_transaction is not null && _transaction.Connection is null) + { + _transaction = null; + } + + return _transaction; + } + set + { + // Don't allow the transaction to be changed while in an async operation + if (_transaction != value && _activeConnection is not null) + { + // If new value... + if (CachedAsyncState != null && CachedAsyncState.PendingAsyncOperation) + { + // If in pending async state, throw. + throw SQL.CannotModifyPropertyAsyncOperationInProgress(); + } + } + + _transaction = value; + + SqlClientEventSource.Log.TryTraceEvent( + "SqlCommand.Set_Transaction | API | " + + $"Object Id {ObjectID}, " + + $"Internal Transaction Id {value?.InternalTransaction?.TransactionId}, " + + $"Client Connection Id {Connection?.ClientConnectionId}"); + } + } + + /// + [DefaultValue(UpdateRowSource.Both)] + [ResCategory(StringsHelper.ResourceNames.DataCategory_Update)] + [ResDescription(StringsHelper.ResourceNames.DbCommand_UpdatedRowSource)] + public override UpdateRowSource UpdatedRowSource + { + get => _updatedRowSource; + set + { + switch (value) + { + case UpdateRowSource.None: + case UpdateRowSource.OutputParameters: + case UpdateRowSource.FirstReturnedRecord: + case UpdateRowSource.Both: + _updatedRowSource = value; + break; + default: + throw ADP.InvalidUpdateRowSource(value); + } + + SqlClientEventSource.Log.TryTraceEvent( + "SqlCommand.UpdatedRowSource | API | " + + $"Object Id {ObjectID}, " + + $"Updated Row Source value {(int)value}, " + + $"Client Connection Id {Connection?.ClientConnectionId}"); + } + } + + #endregion + + #region Internal/Protected/Private Properties + internal bool InPrepare => _inPrepare; + + internal int InternalRecordsAffected + { + get => _rowsAffected; + set + { + if (_rowsAffected == -1) + { + _rowsAffected = value; + } + else if (value > 0) + { + _rowsAffected += value; + } + } + } + // @TODO: Rename to match conventions. internal int ObjectID { get; } = Interlocked.Increment(ref _objectTypeCount); + internal SqlStatistics Statistics + { + get + { + if (_activeConnection is not null) + { + #if NET + bool isStatisticsEnabled = _activeConnection.StatisticsEnabled || + s_diagnosticListener.IsEnabled(SqlClientCommandAfter.Name); + #else + bool isStatisticsEnabled = _activeConnection.StatisticsEnabled; + #endif + + if (isStatisticsEnabled) + { + return _activeConnection.Statistics; + } + } + + return null; + } + } + + /// + protected override DbConnection DbConnection + { + get => Connection; + // @TODO: Does set need a trace event like DbTransaction? + set => Connection = (SqlConnection)value; + } + + /// + protected override DbParameterCollection DbParameterCollection + { + get => Parameters; + } + + /// + protected override DbTransaction DbTransaction + { + get => Transaction; + set + { + // @TODO: Does this need a trace event, we have one in Transaction? + Transaction = (SqlTransaction)value; + SqlClientEventSource.Log.TryTraceEvent( + "SqlCommand.Set_DbTransaction | API | " + + $"Object Id {ObjectID}, " + + $"Client Connection Id {Connection?.ClientConnectionId}"); + } + } + + private int DefaultCommandTimeout + { + // @TODO: Should use connection? Should DefaultCommandTimeout be defined *in the command object*? + get => _activeConnection?.CommandTimeout ?? ADP.DefaultCommandTimeout; + } + + // @TODO: Should be used in more than one place to justify its existence + private SqlInternalConnectionTds InternalTdsConnection + { + // @TODO: Should check for null? Should use Connection? + get => (SqlInternalConnectionTds)_activeConnection.InnerConnection; + } + + private bool IsColumnEncryptionEnabled + { + get + { + bool isEncryptionEnabled = + _columnEncryptionSetting is SqlCommandColumnEncryptionSetting.Enabled || + (_columnEncryptionSetting is SqlCommandColumnEncryptionSetting.UseConnectionSetting && + _activeConnection.IsColumnEncryptionSettingEnabled); + + // Order matters here b/c 1) _activeConnection.Parser can throw if the underlying + // connection is closed, and 2) we do not want to throw in that situation unless + // the user is using transparent parameter encryption (breaks old behavior). + return isEncryptionEnabled && + _activeConnection.Parser is not null && + _activeConnection.Parser.IsColumnEncryptionSupported; + } + } + private bool IsDirty { get @@ -146,6 +669,7 @@ private bool IsDirty { _parameters.IsDirty = _dirty; } + _cachedMetaData = null; } } @@ -252,7 +776,7 @@ private void InternalPrepare() { if (IsDirty) { - Debug.Assert(_cachedMetaData == null || !_dirty, "dirty query should not have cached metadata!"); // can have cached metadata if dirty because of parameters + Debug.Assert(_cachedMetaData is null || !_dirty, "dirty query should not have cached metadata!"); // Someone changed the command text or the parameter schema so we must unprepare the command Unprepare(); @@ -277,6 +801,11 @@ private void InternalPrepare() Statistics?.SafeIncrement(ref Statistics._prepares); } + private void PropertyChanging() + { + IsDirty = true; + } + private void Unprepare() { Debug.Assert(IsPrepared, "Invalid attempt to Unprepare a non-prepared command!"); 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 4152245971..e03630bafb 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/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs index 88f99a2f51..206387c185 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -244,8 +244,17 @@ public static void Test_WithGuidValue_ShouldReturnGuid() var expectedGuid = Guid.NewGuid(); var cmd = new SqlCommand("select @input", conn); cmd.Parameters.AddWithValue("@input", expectedGuid); + var result = cmd.ExecuteScalar(); Assert.Equal(expectedGuid, (Guid)result); + + cmd.Parameters[0].Value = new SqlGuid(expectedGuid); + result = cmd.ExecuteScalar(); + Assert.Equal(expectedGuid, (Guid)result); + + cmd.Parameters[0].Value = SqlGuid.Null; + result = cmd.ExecuteScalar(); + Assert.Equal(DBNull.Value, result); } // Synapse: Parse error at line: 1, column: 8: Incorrect syntax near 'TYPE'. 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/UnitTests/.editorconfig b/src/Microsoft.Data.SqlClient/tests/UnitTests/.editorconfig new file mode 100644 index 0000000000..c75dd30b68 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/.editorconfig @@ -0,0 +1,11 @@ +# editorconfig.org + +# top-most EditorConfig file +root = false + +[*.cs] + +csharp_style_var_when_type_is_apparent = false:refactor +# IDE0090: Use 'new(...)' +csharp_style_implicit_object_creation_when_type_is_apparent = true:warning + diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj index 2f0e12c922..fcef431b36 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj @@ -7,6 +7,7 @@ $(ObjFolder)$(Configuration).$(Platform).$(AssemblyName) $(BinFolder)$(Configuration).$(Platform).$(AssemblyName) true + enable @@ -25,6 +26,7 @@ all + diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/TickCountElapsedTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/TickCountElapsedTest.cs new file mode 100644 index 0000000000..38a555d356 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/TickCountElapsedTest.cs @@ -0,0 +1,56 @@ +// 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 Microsoft.Data.Common; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests; + +/// +/// Tests for Environment.TickCount elapsed time calculation with wraparound handling. +/// +public sealed class TickCountElapsedTest +{ + /// + /// Verifies that normal elapsed time calculation works correctly. + /// + [Fact] + public void CalculateTickCountElapsed_NormalCase_ReturnsCorrectElapsed() + { + uint elapsed = ADP.CalculateTickCountElapsed(1000, 1500); + Assert.Equal(500u, elapsed); + } + + /// + /// Verifies that wraparound from int.MaxValue to int.MinValue is handled correctly. + /// + [Fact] + public void CalculateTickCountElapsed_MaxWraparound_ReturnsOne() + { + uint elapsed = ADP.CalculateTickCountElapsed(int.MaxValue, int.MinValue); + Assert.Equal(1u, elapsed); + } + + /// + /// Verifies that partial wraparound scenarios work correctly. + /// + [Theory] + [InlineData(2147483600, -2147483600, 96u)] + [InlineData(2147483647, -2147483647, 2u)] + public void CalculateTickCountElapsed_PartialWraparound_ReturnsCorrectElapsed(long start, long end, uint expected) + { + uint elapsed = ADP.CalculateTickCountElapsed(start, end); + Assert.Equal(expected, elapsed); + } + + /// + /// Verifies that zero elapsed time returns zero. + /// + [Fact] + public void CalculateTickCountElapsed_ZeroElapsed_ReturnsZero() + { + uint elapsed = ADP.CalculateTickCountElapsed(1000, 1000); + Assert.Equal(0u, elapsed); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/UdtSerialization/InvalidSerializationTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/UdtSerialization/InvalidSerializationTest.cs new file mode 100644 index 0000000000..246d9a73f0 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/UdtSerialization/InvalidSerializationTest.cs @@ -0,0 +1,62 @@ +// 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 Microsoft.Data.SqlClient.Server; +using Microsoft.Data.SqlClient.UnitTests.UdtSerialization.SerializedTypes; +using Microsoft.SqlServer.Server; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.UdtSerialization; + +/// +/// Attempts to serialize types which do not meet the requirements for either user-defined or native serialization. +/// +public sealed class InvalidSerializationTest : IDisposable +{ + private readonly MemoryStream _stream; + + /// + /// Initializes the MemoryStream used for all tests in this class. + /// + public InvalidSerializationTest() + { + _stream = new MemoryStream(); + } + + void IDisposable.Dispose() + { + _stream.Dispose(); + } + + /// + /// Attempts to serialize a class that does not have the SqlUserDefinedType attribute. Verifies that this fails. + /// + [Fact] + public void Serialize_MissingSqlUserDefinedTypeAttribute_Throws() + { + Action serialize = () => SerializationHelperSql9.Serialize(_stream, new ClassMissingSqlUserDefinedTypeAttribute()); + var exception = Assert.Throws(serialize); + + Assert.Equal($"'{typeof(ClassMissingSqlUserDefinedTypeAttribute).FullName}' is an invalid user defined type, reason: no UDT attribute.", exception.Message); + } + + /// + /// Attempts to serialize a class that has a SqlUserDefinedType attribute, but specifies a Format enumeration value of + /// Unknown. Verifies that this fails. + /// + [Fact] + public void Serialize_UnknownFormattedType_Throws() + { + Action serialize = () => SerializationHelperSql9.Serialize(_stream, new UnknownFormattedClass()); + var exception = Assert.Throws("Format", serialize); + +#if NET + Assert.Equal("The Format enumeration value, 0, is not supported by the format method. (Parameter 'Format')", exception.Message); +#else + Assert.Equal("The Format enumeration value, Unknown, is not supported by the format method.\r\nParameter name: Format", exception.Message); +#endif + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/UdtSerialization/NativeSerializationTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/UdtSerialization/NativeSerializationTest.cs new file mode 100644 index 0000000000..3369a66985 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/UdtSerialization/NativeSerializationTest.cs @@ -0,0 +1,491 @@ +// 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 Microsoft.Data.SqlClient.Server; +using Microsoft.Data.SqlClient.UnitTests.UdtSerialization.SerializedTypes; +using System; +using System.Collections.Generic; +using System.Data.SqlTypes; +using System.IO; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.UdtSerialization; + +/// +/// Tests the serialization method defined by MS-SSCLRT. Ensures that combinations of primitives and custom types round-trip. +/// +/// +public sealed class NativeSerializationTest : IDisposable +{ + private readonly MemoryStream _stream; + + /// + /// Initializes the MemoryStream used for all tests in this class. + /// + public NativeSerializationTest() + { + _stream = new MemoryStream(); + } + + void IDisposable.Dispose() + { + _stream.Dispose(); + } + + /// + /// Provides a collection of test data representing non-null primitive type values and their corresponding + /// serialized byte arrays. + /// + /// + public static TheoryData SerializedNonNullPrimitiveTypeValues() => + new() + { + { + new BoolWrapperStruct { Field1 = true }, + new byte[] { 0x01 } + }, + { + new ByteWrapperStruct { Field1 = 0x20 }, + new byte[] { 0x20 } + }, + { + new SByteWrapperStruct { Field1 = -0x1 }, + new byte[] { 0x7F } + }, + { + new UShortWrapperStruct { Field1 = 0x8000 }, + new byte[] { 0x80, 0x00 } + }, + { + new ShortWrapperStruct { Field1 = 0x1234 }, + new byte[] { 0x92, 0x34 } + }, + { + new UIntWrapperStruct { Field1 = 0xFFFFFFFF }, + new byte[] { 0xFF, 0xFF, 0xFF, 0xFF } + }, + { + new IntWrapperStruct { Field1 = -0x12345678 }, + new byte[] { 0x6D, 0xCB, 0xA9, 0x88 } + }, + { + new ULongWrapperStruct { Field1 = ulong.MaxValue }, + new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + { + new LongWrapperStruct { Field1 = long.MinValue }, + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } + }, + { + new FloatWrapperStruct { Field1 = -0 }, + new byte[] { 0x80, 0x00, 0x00, 0x00 } + }, + { + new DoubleWrapperStruct { Field1 = Math.PI }, + new byte[] { 0xC0, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18 } + }, + { + new SqlByteWrapperStruct { Field1 = 0x20 }, + new byte[] { 0x01, 0x20 } + }, + { + new SqlInt16WrapperStruct { Field1 = 0x1234 }, + new byte[] { 0x01, 0x92, 0x34 } + }, + { + new SqlInt32WrapperStruct { Field1 = -0x12345678 }, + new byte[] { 0x01, 0x6D, 0xCB, 0xA9, 0x88 } + }, + { + new SqlInt64WrapperStruct { Field1 = long.MinValue }, + new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } + }, + { + new SqlBooleanWrapperStruct { Field1 = false }, + new byte[] { 0x01 } + }, + { + new SqlSingleWrapperStruct { Field1 = -1 }, + new byte[] { 0x01, 0x40, 0x7F, 0xFF, 0xFF } + }, + { + new SqlDoubleWrapperStruct { Field1 = -Math.PI }, + new byte[] { 0x01, 0x3F, 0xF6, 0xDE, 0x04, 0xAB, 0xBB, 0xD2, 0xE7 } + }, + { + new SqlDateTimeWrapperStruct { Field1 = new DateTime(2000, 1, 1, 12, 34, 56, 500) }, + new byte[] { 0x01, 0x80, 0x00, 0x8E, 0xAC, 0x80, 0xCF, 0x59, 0xD6 } + }, + { + new SqlMoneyWrapperStruct { Field1 = 1.10m }, + new byte[] { 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xF8 } + } + }; + + /// + /// Provides a collection of test data representing serialized values of nested non-null primitive types. + /// + /// + public static TheoryData SerializedNestedNonNullPrimitiveTypeValues() => + new() + { + { + new NestedBoolWrapperStruct { Field1 = true, Field2 = new BoolWrapperStruct { Field1 = false } }, + new byte[] + { + // Field1 + 0x01, + // Field2 + 0x00 + } + }, + { + new NestedByteWrapperStruct { Field1 = 0x20, Field2 = new ByteWrapperStruct { Field1 = 0x30 } }, + new byte[] + { + // Field1 + 0x20, + // Field2 + 0x30 + } + }, + { + new NestedSByteWrapperStruct { Field1 = -0x01, Field2 = new SByteWrapperStruct { Field1 = 0x01 } }, + new byte[] + { + // Field1 + 0x7F, + // Field2 + 0x81 + } + }, + { + new NestedUShortWrapperStruct { Field1 = 0x8000, Field2 = new UShortWrapperStruct { Field1 = 0x8014 } }, + new byte[] + { + // Field1 + 0x80, 0x00, + // Field2.Field1 + 0x80, 0x14 + } + }, + { + new NestedShortWrapperStruct { Field1 = 0x1234, Field2 = new ShortWrapperStruct { Field1 = 0x4321 } }, + new byte[] + { + // Field1 + 0x92, 0x34, + // Field2.Field1 + 0xC3, 0x21 + } + }, + { + new NestedUIntWrapperStruct { Field1 = 0xFFFFFFFF, Field2 = new UIntWrapperStruct { Field1 = 0x00000000 } }, + new byte[] + { + // Field1 + 0xFF, 0xFF, 0xFF, 0xFF, + // Field2.Field1 + 0x00, 0x00, 0x00, 0x00 + } + }, + { + new NestedIntWrapperStruct { Field1 = -0x12345678, Field2 = new IntWrapperStruct { Field1 = 0x12345678 } }, + new byte[] + { + /// Field1 + 0x6D, 0xCB, 0xA9, 0x88, + // Field2.Field1 + 0x92, 0x34, 0x56, 0x78 + } + }, + { + new NestedULongWrapperStruct { Field1 = ulong.MaxValue, Field2 = new ULongWrapperStruct { Field1 = long.MaxValue } }, + new byte[] + { + // Field1 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // Field2.Field1 + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + } + }, + { + new NestedLongWrapperStruct { Field1 = long.MinValue, Field2 = new LongWrapperStruct { Field1 = long.MaxValue } }, + new byte[] + { + // Field1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Field2.Field1 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + } + }, + { + new NestedFloatWrapperStruct { Field1 = -0, Field2 = new FloatWrapperStruct { Field1 = +0 } }, + new byte[] + { + // Field1 + 0x80, 0x00, 0x00, 0x00, + // Field2.Field1 + 0x80, 0x00, 0x00, 0x00 + } + }, + { + new NestedDoubleWrapperStruct { Field1 = Math.PI, Field2 = new DoubleWrapperStruct { Field1 = Math.PI } }, + new byte[] + { + // Field1 + 0xC0, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18, + // Field2.Field1 + 0xC0, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18 + } + }, + { + new NestedSqlByteWrapperStruct { Field1 = 0x20, Field2 = new SqlByteWrapperStruct { Field1 = 0x30 } }, + new byte[] + { + // Field1 + 0x01, 0x20, + // Field2.Field1 + 0x01, 0x30 + } + }, + { + new NestedSqlInt16WrapperStruct { Field1 = 0x1234, Field2 = new SqlInt16WrapperStruct { Field1 = 0x4321 } }, + new byte[] + { + // Field1 + 0x01, 0x92, 0x34, + // Field2.Field1 + 0x01, 0xC3, 0x21 + } + }, + { + new NestedSqlInt32WrapperStruct { Field1 = -0x12345678, Field2 = new SqlInt32WrapperStruct { Field1 = 0x12345678 } }, + new byte[] + { + // Field1 + 0x01, 0x6D, 0xCB, 0xA9, 0x88, + // Field2.Field1 + 0x01, 0x92, 0x34, 0x56, 0x78 + } + }, + { + new NestedSqlInt64WrapperStruct { Field1 = long.MinValue, Field2 = new SqlInt64WrapperStruct { Field1 = long.MaxValue } }, + new byte[] + { + // Field1 + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Field2.Field1 + 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + } + }, + { + new NestedSqlBooleanWrapperStruct { Field1 = false, Field2 = new SqlBooleanWrapperStruct { Field1 = true } }, + new byte[] + { + // Field1 + 0x01, + // Field2.Field1 + 0x02 + } + }, + { + new NestedSqlSingleWrapperStruct { Field1 = -0, Field2 = new SqlSingleWrapperStruct { Field1 = +0 } }, + new byte[] + { + // Field1 + 0x01, 0x80, 0x00, 0x00, 0x00, + // Field2.Field1 + 0x01, 0x80, 0x00, 0x00, 0x00 + } + }, + { + new NestedSqlDoubleWrapperStruct { Field1 = Math.PI, Field2 = new SqlDoubleWrapperStruct { Field1 = Math.PI } }, + new byte[] + { + // Field1 + 0x01, 0xC0, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18, + // Field2.Field1 + 0x01, 0xC0, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18 + } + }, + { + new NestedSqlDateTimeWrapperStruct { Field1 = new DateTime(2000, 1, 1, 12, 34, 56, 500), Field2 = new SqlDateTimeWrapperStruct { Field1 = new DateTime(2000, 1, 1) } }, + new byte[] + { + // Field1 + 0x01, 0x80, 0x00, 0x8E, 0xAC, 0x80, 0xCF, 0x59, 0xD6, + // Field2.Field1 + 0x01, 0x80, 0x00, 0x8E, 0xAC, 0x80, 0x00, 0x00, 0x00 + } + }, + { + new NestedSqlMoneyWrapperStruct { Field1 = 1.10m, Field2 = new SqlMoneyWrapperStruct { Field1 = -2.55m } }, + new byte[] + { + // Field1 + 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xF8, + // Field2.Field1 + 0x01, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9C, 0x64 + } + } + }; + + /// + /// Provides a collection of test data representing serialized null values for various primitive types. + /// + /// + public static TheoryData SerializedNullPrimitiveTypeValues() => + new() + { + { + new SqlByteWrapperStruct { Field1 = SqlByte.Null }, + new byte[] { 0x00, 0x00 } + }, + { + new SqlInt16WrapperStruct { Field1 = SqlInt16.Null }, + new byte[] { 0x00, 0x80, 0x00 } + }, + { + new SqlInt32WrapperStruct { Field1 = SqlInt32.Null }, + new byte[] { 0x00, 0x80, 0x00, 0x00, 0x00 } + }, + { + new SqlInt64WrapperStruct { Field1 = SqlInt64.Null }, + new byte[] { 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } + }, + { + new SqlBooleanWrapperStruct { Field1 = SqlBoolean.Null }, + new byte[] { 0x00 } + }, + { + new SqlSingleWrapperStruct { Field1 = SqlSingle.Null }, + new byte[] { 0x00, 0x80, 0x00, 0x00, 0x00 } + }, + { + new SqlDoubleWrapperStruct { Field1 = SqlDouble.Null }, + new byte[] { 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } + }, + { + new SqlDateTimeWrapperStruct { Field1 = SqlDateTime.Null }, + new byte[] { 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00 } + }, + { + new SqlMoneyWrapperStruct { Field1 = SqlMoney.Null }, + new byte[] { 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } + } + }; + + /// + /// Attempts to serialize various structs containing non-null primitive types. + /// Verifies that the method does not throw, that serialized byte output is correct, and that the value round-trips. + /// + /// Primitive to serialize and to compare against. + /// Expected byte output. + [Theory] + [MemberData(nameof(SerializedNonNullPrimitiveTypeValues))] + public void Serialize_PrimitiveType_Roundtrips(object primitive, byte[] expectedValue) => + RoundtripType(primitive, expectedValue); + + /// + /// Attempts to serialize a nested struct hierarchy containing non-null primitive types. + /// Verifies that the method does not throw, that serialized byte output is correct, and that the value round-trips. + /// + /// Primitive to serialize and to compare against. + /// Expected byte output. + [Theory] + [MemberData(nameof(SerializedNestedNonNullPrimitiveTypeValues))] + public void Serialize_NestedPrimitiveType_Roundtrips(object primitive, byte[] expectedValue) => + RoundtripType(primitive, expectedValue); + + /// + /// Attempts to serialize various structs containing null-valued primitive types. + /// Verifies that the method does not throw, that serialized byte output is correct, and that the value round-trips. + /// + /// Primitive to serialize and to compare against. + /// Expected byte output. + [Theory] + [MemberData(nameof(SerializedNullPrimitiveTypeValues))] + public void Serialize_NullPrimitiveType_Roundtrips(object primitive, byte[] expectedValue) => + RoundtripType(primitive, expectedValue); + + /// + /// Attempts to serialize an instance of a class. + /// + /// + [Fact] + public void Serialize_TopLevelClass_Succeeds() + { + NestedBoolWrapperClass validWrapper = new() + { + Field1 = true, + Field2 = new BoolWrapperStruct() { Field1 = true } + }; + + SerializationHelperSql9.Serialize(_stream, validWrapper); + } + + /// + /// Attempts to serialize a field referring to an instance of a class. + /// Verifies that this fails, and that Native format serialization only operates with primitive types and value types containing these. + /// + /// + [Fact] + public void Serialize_NestedClass_Throws() + { + InvalidNestedBoolWrapperClass invalidWrapper = new() + { + Field1 = true, + Field2 = new BoolWrapperClass() { Field1 = true } + }; + + var ex = Assert.Throws(() => SerializationHelperSql9.Serialize(_stream, invalidWrapper)); + string expectedException = StringsHelper.GetString(Strings.SQL_CannotCreateNormalizer, invalidWrapper.Field2.GetType().FullName); + + Assert.Equal(expectedException, ex.Message); + } + + /// + /// Attempts to serialize a struct containing non-primitive value types. + /// Verifies that this fails. + /// + [Fact] + public void Serialize_NonPrimitiveType_Throws() + { + InvalidIntPtrAndByteWrapperStruct invalidWrapper = new() + { + Field1 = 1, + Field2 = IntPtr.Zero + }; + + var ex = Assert.Throws(() => SerializationHelperSql9.Serialize(_stream, invalidWrapper)); + string expectedException = StringsHelper.GetString(Strings.SQL_CannotCreateNormalizer, invalidWrapper.Field2.GetType().FullName); + + Assert.Equal(expectedException, ex.Message); + } + + /// + /// Serializes an object, verifies the value and the size of the object, then roundtrips it and verifies the result is identical. + /// + /// Object to serialize. + /// Expected serialization output. + private void RoundtripType(object inputValue, byte[] expectedValue) + { + int typeSize = SerializationHelperSql9.SizeInBytes(inputValue.GetType()); + int objectSize = SerializationHelperSql9.SizeInBytes(inputValue); + int maxTypeSize = SerializationHelperSql9.GetUdtMaxLength(inputValue.GetType()); + + SerializationHelperSql9.Serialize(_stream, inputValue); + _stream.Seek(0, SeekOrigin.Begin); + object readPrimitive = SerializationHelperSql9.Deserialize(_stream, inputValue.GetType()); + + // For native formatting, the type size, the object size and the maximum object size will always be identical + Assert.Equal(typeSize, objectSize); + Assert.Equal(expectedValue.Length, typeSize); + Assert.Equal(typeSize, maxTypeSize); + + Assert.Equal(expectedValue, _stream.ToArray()); + Assert.Equal(inputValue, readPrimitive); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/UdtSerialization/SerializedTypes.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/UdtSerialization/SerializedTypes.cs new file mode 100644 index 0000000000..a02c213e01 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/UdtSerialization/SerializedTypes.cs @@ -0,0 +1,335 @@ +// 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 Microsoft.SqlServer.Server; +using System; +using System.Data.SqlTypes; +using System.IO; +using System.Runtime.InteropServices; + +namespace Microsoft.Data.SqlClient.UnitTests.UdtSerialization.SerializedTypes; + +// Simple cases: a struct containing one of the designated primitive types +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct BoolWrapperStruct { public bool Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct ByteWrapperStruct { public byte Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct SByteWrapperStruct { public sbyte Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct UShortWrapperStruct { public ushort Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct ShortWrapperStruct { public short Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct UIntWrapperStruct { public uint Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct IntWrapperStruct { public int Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct ULongWrapperStruct { public ulong Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct LongWrapperStruct { public long Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct FloatWrapperStruct { public float Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct DoubleWrapperStruct { public double Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct SqlByteWrapperStruct { public SqlByte Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct SqlInt16WrapperStruct { public SqlInt16 Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct SqlInt32WrapperStruct { public SqlInt32 Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct SqlInt64WrapperStruct { public SqlInt64 Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct SqlBooleanWrapperStruct { public SqlBoolean Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct SqlSingleWrapperStruct { public SqlSingle Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct SqlDoubleWrapperStruct { public SqlDouble Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct SqlDateTimeWrapperStruct { public SqlDateTime Field1; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct SqlMoneyWrapperStruct { public SqlMoney Field1; } + + +// Success case: a class containing one of the designated primitive types +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal class BoolWrapperClass { public bool Field1; } + +// Success case: a struct containing one designated primitive type and one nested struct +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedBoolWrapperStruct { public bool Field1; public BoolWrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedByteWrapperStruct { public byte Field1; public ByteWrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedSByteWrapperStruct { public sbyte Field1; public SByteWrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedUShortWrapperStruct { public ushort Field1; public UShortWrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedShortWrapperStruct { public short Field1; public ShortWrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedUIntWrapperStruct { public uint Field1; public UIntWrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedIntWrapperStruct { public int Field1; public IntWrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedULongWrapperStruct { public ulong Field1; public ULongWrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedLongWrapperStruct { public long Field1; public LongWrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedFloatWrapperStruct { public float Field1; public FloatWrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedDoubleWrapperStruct { public double Field1; public DoubleWrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedSqlByteWrapperStruct { public SqlByte Field1; public SqlByteWrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedSqlInt16WrapperStruct { public SqlInt16 Field1; public SqlInt16WrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedSqlInt32WrapperStruct { public SqlInt32 Field1; public SqlInt32WrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedSqlInt64WrapperStruct { public SqlInt64 Field1; public SqlInt64WrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedSqlBooleanWrapperStruct { public SqlBoolean Field1; public SqlBooleanWrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedSqlSingleWrapperStruct { public SqlSingle Field1; public SqlSingleWrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedSqlDoubleWrapperStruct { public SqlDouble Field1; public SqlDoubleWrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedSqlDateTimeWrapperStruct { public SqlDateTime Field1; public SqlDateTimeWrapperStruct Field2; } + +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct NestedSqlMoneyWrapperStruct { public SqlMoney Field1; public SqlMoneyWrapperStruct Field2; } + + +// Success case: a class containing one designated primitive type and a nested struct +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal class NestedBoolWrapperClass { public bool Field1; public BoolWrapperStruct Field2; } + +// Failure case: a struct or a class containing one designated primitive type and a nested class +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal class InvalidNestedBoolWrapperClass { public bool Field1; public BoolWrapperClass? Field2; } + +// Failure case: a struct or a class containing a field which is not a designated primitive type +[SqlUserDefinedType(Format.Native)] +[StructLayout(LayoutKind.Sequential)] +internal struct InvalidIntPtrAndByteWrapperStruct { public byte Field1; public IntPtr Field2; } + +// Success case: a struct or a class implementing IBinarySerialize which would not otherwise be serializable +internal interface IFormattingProgress +{ + bool ParameterlessConstructorInvoked { get; } + bool ReadInvoked { get; } + bool WriteInvoked { get; } +} + +[SqlUserDefinedType(Format.UserDefined, MaxByteSize = 11)] +internal struct UserDefinedFormattedStruct : IBinarySerialize, IFormattingProgress, IEquatable +{ + public IntPtr Field1; + public bool ParameterlessConstructorInvoked { get; } + public bool ReadInvoked { get; private set; } + public bool WriteInvoked { get; private set; } + + public UserDefinedFormattedStruct() + { + ParameterlessConstructorInvoked = true; + } + + public UserDefinedFormattedStruct(IntPtr field1) + { + Field1 = field1; + } + + public void Read(BinaryReader r) + { + Field1 = IntPtr.Size switch + { + sizeof(uint) => (IntPtr)r.ReadUInt32(), + sizeof(ulong) => (IntPtr)r.ReadUInt64(), + _ => throw new Exception("Invalid IntPtr size") + }; + + ReadInvoked = true; + } + + public void Write(BinaryWriter w) + { + if (IntPtr.Size == sizeof(uint)) + { + w.Write((uint)Field1); + } + else if (IntPtr.Size == sizeof(ulong)) + { + w.Write((ulong)Field1); + } + else + { + throw new Exception("Invalid IntPtr size"); + } + + WriteInvoked = true; + } + + public bool Equals(UserDefinedFormattedStruct other) + => other.Field1 == Field1; +} + +[SqlUserDefinedType(Format.UserDefined, MaxByteSize = 11)] +internal class UserDefinedFormattedClass : IBinarySerialize, IFormattingProgress, IEquatable +{ + public IntPtr Field1; + public bool ParameterlessConstructorInvoked { get; } + public bool ReadInvoked { get; private set; } + public bool WriteInvoked { get; private set; } + + public UserDefinedFormattedClass() + { + ParameterlessConstructorInvoked = true; + } + + public UserDefinedFormattedClass(IntPtr field1) + { + Field1 = field1; + } + + public void Read(BinaryReader r) + { + Field1 = IntPtr.Size switch + { + sizeof(uint) => (IntPtr)r.ReadUInt32(), + sizeof(ulong) => (IntPtr)r.ReadUInt64(), + _ => throw new Exception("Invalid IntPtr size") + }; + + ReadInvoked = true; + } + + public void Write(BinaryWriter w) + { + if (IntPtr.Size == sizeof(uint)) + { + w.Write((uint)Field1); + } + else if (IntPtr.Size == sizeof(ulong)) + { + w.Write((ulong)Field1); + } + else + { + throw new Exception("Invalid IntPtr size"); + } + + WriteInvoked = true; + } + + public bool Equals(UserDefinedFormattedClass? other) + => other is not null && other.Field1 == Field1; +} + +// Failure cases: type does not have a public constructor, does not implement IBinarySerialize, does not have a SqlUserDefinedType attribute, +// or has a SqlUserDefinedType attribute with a Format of Unknown. + +[SqlUserDefinedType(Format.UserDefined)] +internal class UserDefinedMissingPublicConstructor : IBinarySerialize +{ + public UserDefinedMissingPublicConstructor(bool _) { } + + public void Read(BinaryReader r) { } + + public void Write(BinaryWriter w) { } +} + +[SqlUserDefinedType(Format.UserDefined)] +internal class UserDefinedDoesNotImplementIBinarySerialize +{ + public UserDefinedDoesNotImplementIBinarySerialize() { } +} + +internal class ClassMissingSqlUserDefinedTypeAttribute +{ +} + +[SqlUserDefinedType(Format.Unknown)] +internal class UnknownFormattedClass +{ +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/UdtSerialization/UserDefinedSerializationTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/UdtSerialization/UserDefinedSerializationTest.cs new file mode 100644 index 0000000000..4d7ea799fd --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/UdtSerialization/UserDefinedSerializationTest.cs @@ -0,0 +1,123 @@ +// 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 Microsoft.Data.SqlClient.Server; +using Microsoft.Data.SqlClient.UnitTests.UdtSerialization.SerializedTypes; +using System; +using System.Collections.Generic; +using System.IO; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.UdtSerialization; + +/// +/// Tests the user-defined UDT serialization method. Verifies that custom types round-trip. +/// +public sealed class UserDefinedSerializationTest : IDisposable +{ + private readonly MemoryStream _stream; + + /// + /// Initializes the MemoryStream used for all tests in this class. + /// + public UserDefinedSerializationTest() + { + _stream = new MemoryStream(); + } + + void IDisposable.Dispose() + { + _stream.Dispose(); + } + + /// + /// Attempts to serialize and deserialize an instance of a struct with a user-defined serialization method. + /// + /// + [Fact] + public void Serialize_Struct_Roundtrips() => + RoundtripType(new UserDefinedFormattedStruct((IntPtr)0x12345678)); + + /// + /// Attempts to serialize and deserialize an instance of a class with a user-defined serialization method. + /// + /// + [Fact] + public void Serialize_Class_Roundtrips() => + RoundtripType(new UserDefinedFormattedClass((IntPtr)0x12345678)); + + /// + /// Attempts to deserialize an instance of a type with a user-defined serialization method but without a public + /// parameterless constructor. Verifies that this fails. + /// + [Fact] + public void Deserialize_MissingPublicParameterlessConstructor_Throws() + { + SerializationHelperSql9.Serialize(_stream, new UserDefinedMissingPublicConstructor(true)); + _stream.Seek(0, SeekOrigin.Begin); + + Action deserialize = () => SerializationHelperSql9.Deserialize(_stream, typeof(UserDefinedMissingPublicConstructor)); + + Assert.Throws(deserialize); + } + + /// + /// Attempts to deserialize an instance of a type with a user-defined serialization method but which does not, + /// implement IBinarySerialize. Verifies that this fails. + /// + [Fact] + public void Serialize_DoesNotImplementIBinarySerialize_Throws() + { + Action serialize = () => SerializationHelperSql9.Serialize(_stream, new UserDefinedDoesNotImplementIBinarySerialize()); + + Assert.Throws(serialize); + } + + private void RoundtripType(T userObject) + where T : IFormattingProgress + { + int typeSize = SerializationHelperSql9.SizeInBytes(userObject.GetType()); + int objectSize = SerializationHelperSql9.SizeInBytes(userObject); + int maxTypeSize = SerializationHelperSql9.GetUdtMaxLength(userObject.GetType()); + + SerializationHelperSql9.Serialize(_stream, userObject); + _stream.Seek(0, SeekOrigin.Begin); + byte[] serializedValue = _stream.ToArray(); + T readInstance = (T)SerializationHelperSql9.Deserialize(_stream, userObject.GetType()); + + // If this is a struct, it will have been copied by value and the write to WriteInvoked will have been made + // to another copy of our object + if (!typeof(T).IsValueType) + { + Assert.True(userObject.WriteInvoked); + } + + Assert.Equal(IntPtr.Size, typeSize); + Assert.Equal(IntPtr.Size, objectSize); + Assert.Equal(11, maxTypeSize); + + Assert.Equal(IntPtr.Size, serializedValue.Length); + if (IntPtr.Size == 8) + { + Assert.Equal([0x78, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00], serializedValue); + } + else if (IntPtr.Size == 4) + { + Assert.Equal([0x78, 0x56, 0x34, 0x12], serializedValue); + } + else + { + Assert.Fail("Invalid IntPtr size."); + } + + // In .NET Framework, Activator.CreateInstance does not invoke a struct's parameterless constructor +#if NET + Assert.NotEqual(userObject.ParameterlessConstructorInvoked, readInstance.ParameterlessConstructorInvoked); + Assert.True(readInstance.ParameterlessConstructorInvoked); +#endif + Assert.True(readInstance.ReadInvoked); + + Assert.Equal(userObject, readInstance); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlTypeWorkaroundsTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlTypeWorkaroundsTests.cs index f7cd1811ed..cf97f21a39 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlTypeWorkaroundsTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlTypeWorkaroundsTests.cs @@ -20,7 +20,7 @@ public class SqlTypeWorkaroundsTests #region SqlBinary public static TheoryData ByteArrayToSqlBinary_NonNullInput_Data => - new TheoryData + new() { Array.Empty(), new byte[] { 1, 2, 3, 4}, @@ -53,7 +53,7 @@ public void ByteArrayToSqlBinary_NullInput() #region SqlDecimal public static TheoryData SqlDecimalWriteTdsValue_NonNullInput_Data => - new TheoryData + new() { SqlDecimal.MinValue, new SqlDecimal(-1.2345678), @@ -102,7 +102,7 @@ public void SqlDecimalWriteTdsValue_NullInput() #region SqlGuid public static TheoryData ByteArrayToSqlGuid_InvalidInput_Data => - new TheoryData + new() { null, Array.Empty(), @@ -122,7 +122,7 @@ public void ByteArrayToSqlGuid_InvalidInput(byte[]? input) } public static TheoryData ByteArrayToSqlGuid_ValidInput_Data => - new TheoryData + new() { new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 } @@ -145,7 +145,7 @@ public void ByteArrayToSqlGuid_ValidInput(byte[] input) #region SqlMoney public static TheoryData LongToSqlMoney_Data => - new TheoryData + new() { { long.MinValue, SqlMoney.MinValue }, { (long)((decimal)-123000000 / 10000), new SqlMoney(-1.23) }, @@ -166,7 +166,7 @@ public void LongToSqlMoney(long input, SqlMoney expected) } public static TheoryData SqlMoneyToLong_NonNullInput_Data => - new TheoryData + new() { { SqlMoney.MinValue, long.MinValue }, { new SqlMoney(-1.23), (long)(new SqlMoney(-1.23).ToDecimal() * 10000) }, diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/TdsParserInternalsTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/TdsParserInternalsTest.cs index f0b3729d6f..17a4ad8b5a 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/TdsParserInternalsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/TdsParserInternalsTest.cs @@ -92,7 +92,7 @@ public void WriteUserAgentFeatureRequest_WriteTrue_AppendsOnlyExtensionBytes() bufferAfter[start + 5]); // slice into the existing buffer - ReadOnlySpan writtenSpan = new ReadOnlySpan( + ReadOnlySpan writtenSpan = new( bufferAfter, start + 6, appended - 6); diff --git a/tools/props/Versions.props b/tools/props/Versions.props index ad42c4964a..e4935f8c91 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -1,29 +1,45 @@ - + + + - 7.0.0 0 + + + + + 7.0.0 + + $(MdsVersionDefault).$(BuildNumber)-dev + + $(MdsVersionDefault).$(BuildNumber) - + + 7.0.0.0 $(AssemblyFileVersion) - $(MdsVersionDefault)-dev - $(NugetPackageVersion) + + + $(MdsPackageVersion) + + @@ -33,8 +49,11 @@ 1.0.0-dev $(SqlServerPackageVersion) + + - $(NugetPackageVersion) + 7.0.0 + $(AkvVersionDefault).$(BuildNumber)-dev - $(NugetPackageVersion) + $(MdsPackageVersion) diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index 67d0078279..204f6b110b 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -32,6 +32,7 @@ + @@ -48,6 +49,7 @@ + @@ -62,6 +64,7 @@ + @@ -76,6 +79,7 @@ + diff --git a/tools/targets/GenerateMdsPackage.targets b/tools/targets/GenerateMdsPackage.targets new file mode 100644 index 0000000000..033b9a338b --- /dev/null +++ b/tools/targets/GenerateMdsPackage.targets @@ -0,0 +1,17 @@ + + + + + $(MdsPackageVersion)-debug + + + + + + + + + + + diff --git a/tools/targets/GenerateNugetPackage.targets b/tools/targets/GenerateSqlServerPackage.targets similarity index 53% rename from tools/targets/GenerateNugetPackage.targets rename to tools/targets/GenerateSqlServerPackage.targets index 4c8cea4159..f7a2da0301 100644 --- a/tools/targets/GenerateNugetPackage.targets +++ b/tools/targets/GenerateSqlServerPackage.targets @@ -1,19 +1,5 @@ - - - $(NugetPackageVersion)-debug - - - - - - - - - - $(SqlServerPackageVersion)-debug diff --git a/tools/targets/add-ons/GenerateAKVProviderNugetPackage.targets b/tools/targets/add-ons/GenerateAkvPackage.targets similarity index 51% rename from tools/targets/add-ons/GenerateAKVProviderNugetPackage.targets rename to tools/targets/add-ons/GenerateAkvPackage.targets index 78da74bf32..f63afe95a8 100644 --- a/tools/targets/add-ons/GenerateAKVProviderNugetPackage.targets +++ b/tools/targets/add-ons/GenerateAkvPackage.targets @@ -1,12 +1,12 @@ - + - $(NugetPackageVersion)-debug + $(AkvPackageVersion)-debug - + - +